next up previous contents
Next: Constrained Property Up: Bound Property と Previous: プロパティをbindする

BeanBoxでのProperty Bind

基本的な動きが確認できたら、すこし、詳しくプログラムの働きを見てみる ことにしましょう。BeanBox内での二つのbeanのプロパティの結合は、Editメニュー でBind Property...を選んだときに起動される、BeanBoxクラスのdoBindメソッドに よって、全てコントロールされています。つぎのリストを見て下さい。

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

PropertyNameDialogが行う、もっとも基本的な仕事は、Introspectionの働きを 使って、指定されたbeanのすべてのプロパティの配列を作ることです。 PropertyDescriptor allProperties[];とすると、BeanInfo経由で、次のシーケンスで すべてのプロパティの配列を獲得することが出来ます。これは、以前にも、beansの イディオムとして紹介したものです。

   allProperties = 
      Introspector.getBeanInfo(source.getClass()).getPropertyDescriptors();

PropertyNameDialogは、doBindメソッドの中では、ソース側とターゲット側とで、 二回呼び出されます。このクラスのコンストラクタの最後の引数のreadableが、 この違いを表現しています。すべてのプロパティがこのPropertyNameDialogに 表示されるかというと、そうではありません。PropertyNameDialogは、プロパティの 属性に応じて、表示すべきプロパティを次のようにして選び出し、vector properties に格納します。ここでmatchは、コンストラクタの第三引数です。

  1. ソース側が、PropertyChangeイベントの発生元になっていなければ、スキップ。
  2. ソース側では、そのプロパティにgetterメソッドが存在しなければ、スキップ。
  3. ターゲット側では、そのプロパティにsetterメソッドが存在しなければスキップ。
  4. ソース側では、1.2.3.をクリアした全てのプロパティを、 ターゲット側では、その型が、ソース側で選ばれたプロパティに等しいか、 あるいは、それを拡大したプロパティに限って、表示用のvector である propertiesに入れます。

=============================================================================
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クラス

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);

propertyChangeメソッドの呼び出し

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;
        }
    }


=============================================================================


maruyama@wakhok.ac.jp