基本的な動きが確認できたら、すこし、詳しくプログラムの働きを見てみる ことにしましょう。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;
}
}
=============================================================================