基本的な動きが確認できたら、すこし、詳しくプログラムの働きを見てみる ことにしましょう。BeanBox内での二つのbeanのプロパティの結合は、Editメニュー でBind Property...を選んだときに起動される、BeanBoxクラスのdoBindメソッドに よって、全てコントロールされています。つぎのリストを見て下さい。
つぎのリストを見て下さい。 getCurrentWrapperメソッドは、フォーカスがあるbeanのWrapper(beanを包んでいる 目に見えない入れ物と考えてください。フォーカス時に表われる縞縞の境界線のこと だと思っても構いません)を返します。getBeanメソッドは、Wrapperからbeanを 取り出します。
PropertyNameDialogの働きは、後で見ますが、ここでは二度の呼び出しの目的が、 ソース側とターゲット側で選択された、それぞれのプロパティを得ることであること に注意してください。getConnectionメソッドは、以前のイベント処理の際にも使われ ましたが、sourceから発する赤いrubber bandを張って、targetとなるWrapperを 確定するのに使われています。
結局、内容的な処理は、PropertyHookupクラスのattachメソッドの呼び出しで 行われていることが分かります。attachの引数の中には、二つのWrapperの他に プロパティの名前、このプロパティのsource側でのgetterとtarget側でのsetter が含まれています。
============================================================================= public class BeanBox extends Panel implements Serializable, Runnable, MouseListener, MouseMotionListener { .......... void doBind() { Wrapper sourceWrapper = BeanBoxFrame.getCurrentWrapper(); Object bean = sourceWrapper.getBean(); PropertyNameDialog dialog = new PropertyNameDialog(getFrame(), bean, "Select source property", null, true); PropertyDescriptor sourceProperty = dialog.getResult(); if (sourceProperty == null) { return; } Wrapper targetWrapper = getConnection(sourceWrapper); if (targetWrapper == null) { return; } Object target = targetWrapper.getBean(); dialog = new PropertyNameDialog(getFrame(), target, "Select target property", sourceProperty.getPropertyType(), false); PropertyDescriptor targetProperty = dialog.getResult(); if (targetProperty == null) { return; } PropertyHookup.attach(sourceWrapper, sourceProperty.getName(), sourceProperty.getReadMethod(), targetWrapper, targetProperty.getWriteMethod()); } ............ } =============================================================================
PropertyNameDialogが行う、もっとも基本的な仕事は、Introspectionの働きを 使って、指定されたbeanのすべてのプロパティの配列を作ることです。 PropertyDescriptor allProperties[];とすると、BeanInfo経由で、次のシーケンスで すべてのプロパティの配列を獲得することが出来ます。これは、以前にも、beansの イディオムとして紹介したものです。
allProperties = Introspector.getBeanInfo(source.getClass()).getPropertyDescriptors();
PropertyNameDialogは、doBindメソッドの中では、ソース側とターゲット側とで、 二回呼び出されます。このクラスのコンストラクタの最後の引数のreadableが、 この違いを表現しています。すべてのプロパティがこのPropertyNameDialogに 表示されるかというと、そうではありません。PropertyNameDialogは、プロパティの 属性に応じて、表示すべきプロパティを次のようにして選び出し、vector properties に格納します。ここでmatchは、コンストラクタの第三引数です。
============================================================================= public class PropertyNameDialog extends Dialog implements ActionListener { ...... PropertyNameDialog(Frame frame, Object source, String message, Class match, boolean readable) { ......... ......... properties = new Vector(); for (int i = 0; i < allProperties.length; i++) { PropertyDescriptor pd = allProperties[i]; if (readable && !pd.isBound()) { continue; } if (readable && pd.getReadMethod() == null) { continue; } if (!readable && pd.getWriteMethod() == null) { continue; } if (match == null || isSubclass(match, pd.getPropertyType())) { properties.addElement(pd); } } ......... ......... } ...... } =============================================================================
PropertyHookupクラスのインスタンスは、二つのbeans間で最初にプロパティの bindが行われたときに生成されます。このインスタンスは、ソース側での PropertyChangeイベントを受け取って、自らのPropertyChangeメソッドを呼び出す、 いわゆるadaptorです。
このadaptorのインスタンスに、hookと名前をつけることにしましょう。hookは、 BeanBox中のbeanに対応して、beanの数だけ作られる可能性があります。また、 それぞれのhookは、targetsByPropertyNameというHashtableを抱えています。 hookは、結合されたプロパティのソース側に対応しているのですが、このテーブルは、 そのソースから発せられるPropertyChangeイベントの受け手達(ターゲット)を、 プロパティの名前で整理したものです。
一つのソースbeanの一つのプロパティの変更は、複数のbeansに伝えられることが ありえますので、プロパティ名をキーとしたハッシュテーブル targetsByPropertyName は、複数のtargetsを値に持たなければなりません。ここでは、複数のtargetsは、 Vectorで表現されています。 ターゲットとして必要な情報は、ターゲットのbeanと ソース側で選ばれた特定のプロパティのターゲット上でのsetterですので、 PropertyHookupTarget という新しいクラスが導入されて、そのインスタンスの Vectorが、テーブルに入れられています。
これで、PropertyHookupクラスのattachメソッドの働きを見る準備が整いました。 次のリストを見てください。
============================================================================= class PropertyHookup implements PropertyChangeListener, Serializable { static final long serialVersionUID = 85103197332680806L; Object source; Hashtable targetsByPropertyName; private static Hashtable instances = new Hashtable(); private static Class listenerClass=java.beans.PropertyChangeListener.class; private PropertyHookup(Object source) { this.source = source; targetsByPropertyName = new Hashtable(); } public synchronized static void attach(Wrapper sourceWrapper, String propertyName, Method getter, Wrapper targetWrapper, Method setter) { Object source = sourceWrapper.getBean(); Object targetObject = targetWrapper.getBean(); PropertyHookup hook = (PropertyHookup) instances.get(source); if (hook == null) { // This is the first property hookup on this source object. // Push a PropertyHookup adaptor onto the source. hook = new PropertyHookup(source); instances.put(source, hook); // Register our listener object with the source Wrapper. sourceWrapper.addEventTarget("propertyChange", null, hook); } Vector targets = (Vector) hook.targetsByPropertyName.get(propertyName); if (targets == null) { targets = new Vector(); hook.targetsByPropertyName.put(propertyName, targets); } PropertyHookupTarget target; for (int i = 0; i < targets.size(); i++) { target = (PropertyHookupTarget) targets.elementAt(i); if (target.setter == setter && target.object == targetObject) { // We've already got this hookup. Just return. return; } } targets.addElement(new PropertyHookupTarget(targetObject,setter)); // propagate the initial value. try { Object args1[] = { }; Object value = getter.invoke(source, args1); Object args2[] = { value }; setter.invoke(targetObject, args2); } catch (Exception ex) { System.err.println("Property propagation failed"); ex.printStackTrace(); } } ....... } =============================================================================
いくつかの説明を補足しましょう。
PropertyHookupクラスには、instancesというハッシュテーブルがあります。この テーブルが、staticなテーブルであることに、注意が必要です。先に見たテーブル targetsByPropertyNameは、staticなテーブルではありませんので、クラスの インスタンス毎に存在するのですが、このinstancesは、そうではありません。 どんな働きをしているか考えてください。
sourceWrapper.addEventTarget("propertyChange", null, hook); という シーケンスも、要注意です。ここでは詳しい説明は出来ませんが、ソースのbean上で、 bean.addPropertyChangeListener(hook); が実行されたのと同じ働きをします。 要するに、hookがbeanのPropertyChangeイベントのリスナーとして登録されると 言うことです。
あとは、これも基本的な、JavaBeansのイディオムといってよい次の形があります。 sourceでのプロパティの値をgetterで獲得して、その値を、target上で setterで 設定するというものです。特に、変数の値を使った配列の初期化のスタイルに 注目して下さい。
Object args1[] = { }; Object value = getter.invoke(source, args1); Object args2[] = { value }; setter.invoke(targetObject, args2);
PropertyHookupクラスは、PropertyChangeListenerインターフェースを実装した PropertyChangeイベントのアダプタ・クラスですので、propertyChangeメソッドを 含んでいます。イベント・ソース上でのpropertyChangeイベントの発生は、この adaptorクラス上でのpropertyChangeメソッドの呼び出しを引き起こします。
その働きは、難しくはありません。 まず、イベント・ソースから渡されるイベント・オブジェクトから、プロパティの 名前を獲得します。今度は、この名前を使って、このadaptorのインスタンスが抱えてる argetsByPropertyNameテーブルから、この変更を伝えるべきターゲット達の情報を 獲得します。これは、Vectorになっています。
後は、イベントから設定すべき新しい値を得て、vector上のすべてのオブジェクトに 対して、setterメソッドを呼び出します。setterは、ターゲット毎に異なるかもしれ ませんが、その情報は、vectorの中に含まれていたはずです。
============================================================================= synchronized public void propertyChange(PropertyChangeEvent evt) { String propertyName = evt.getPropertyName(); Vector targets = (Vector) targetsByPropertyName.get(propertyName); if (targets == null) { return; } Object args[] = { evt.getNewValue() }; for (int i = 0; i < targets.size(); i++) { PropertyHookupTarget target = (PropertyHookupTarget)targets.elementAt(i); if (target.active) { // We're already propagating this change. continue; } target.active = true; try { target.setter.invoke(target.object, args); } catch (InvocationTargetException ex) { System.err.println("Property set failed"); ex.printStackTrace(); } catch (Exception ex) { System.err.println("Unexpected Property set exception"); ex.printStackTrace(); } target.active = false; } } =============================================================================