さっきの続き。
ちょっとBindingクラスのメンバを眺めてみるとJSFみたいにConverterやValidatorを仕掛けることが出来るっぽい。
Converterはjavax.beans.binding.BindingConverteを継承して作る。
Validatorはjavax.beans.binding.BindingValidatorを継承して作る。
どういう風に動くのかをトレースしてみた。
トレースのために下みたいなコンバータとバリデータを作った。
// ---------- // PersonConverter.java package okazuki; import javax.beans.binding.BindingConverter; public class PersonConverter extends BindingConverter { public Object sourceToTarget(Object source) { System.out.println(">> converter: source = " + source); return source; } @Override public Object targetToSource(Object target) { System.out.println(">> converter: target = " + target); return target; } } // ---------- // PersonValidator.java package okazuki; import javax.beans.binding.Binding; import javax.beans.binding.BindingValidator; import javax.beans.binding.ValidationResult; public class PersonValidator extends BindingValidator { public PersonValidator() { } public ValidationResult validate(Binding binding, Object value) { System.out.println(">> validator: value = " + value); return null; } }
そして、さっきのmainを下のように書き換えて実行した。
package okazuki; import javax.beans.binding.Binding; import javax.beans.binding.BindingContext; public class Main { public static void main(String[] args) { Person p1 = new Person(); Person p2 = new Person(); BindingContext ctx = new BindingContext(); // Bindingオブジェクトに対してコンバータとバリデータを仕掛ける Binding binding = ctx.addBinding(p1, "${name}", p2, "name"); binding.setConverter(new PersonConverter()); binding.setValidator(new PersonValidator()); ctx.bind(); System.out.println("p1.name change"); p1.setName("太郎"); System.out.println("p1.name = " + p1.getName()); System.out.println("p2.name = " + p2.getName()); System.out.println("p2.name change"); p2.setName("二郎"); System.out.println("p1.name = " + p1.getName()); System.out.println("p2.name = " + p2.getName()); } }
実行すると結果は下のようになった。
p1.name change >> converter: source = 太郎 p1.name = 太郎 p2.name = 太郎 p2.name change >> converter: target = 二郎 >> validator: value = 二郎 p1.name = 二郎 p2.name = 二郎
今更だけど、addBindingのときに最初に渡してるクラスがソースで、後に渡してるクラスがターゲットというらしい。
動きを見てるとこんな感じのサイクルで動いてるのかな。
- Source to Targetの場合
- sourceの値が書き換わる
- ConverterのsourceToTargetメソッド呼び出し
- sourceToTargetの結果をtargetに設定
- Target to Sourceの場合
- targetの値が書き換わる
- ConverterのtargetToSourceメソッド呼び出し
- Validatorのvalidateメソッドの呼び出し
- validateメソッドの結果に応じて動作が変わる
- nullを返した時は普通にsourceの値が書き換わる
- javax.beans.binding.ValidationResultを返した時は、javax.beans.binding.ValidationResultのコンストラクタに設定したActionが実行される
ちなみにValidatorのvalidateメソッドで返すValidationResultに設定するアクションは、デフォルトで下の2つが提供されてる。
- 何もしない
- ValidationResult.Action.DO_NOTHING
- ソースの値をターゲットに設定する
- ValidationResult.Action.SET_TARGET_FROM_SOURCE
Converter/Validatorお試し
PersonクラスにbirthdayというDate型のプロパティを追加して、それをテキストフィールドにバインドする場合の例を書いてみた。
解説はめんどいので、コードを読み解いてもらうといいかも!?
// Person.java package okazuki; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.Date; import java.util.LinkedList; import java.util.List; public class Person { private String name; private Date birthday; private List<PropertyChangeListener> propertyChangeListeners = new LinkedList<PropertyChangeListener>(); public Person() { } public String getName() { return name; } public void setName(String name) { String oldName = this.name; this.name = name; firePropertyChange("name", oldName, name); } public Date getBirthday() { return birthday; } public void setBirthday(Date birthday) { Date old = this.birthday; this.birthday = birthday; firePropertyChange("birthday", old, birthday); } public void addPropertyChangeListener(PropertyChangeListener l) { propertyChangeListeners.add(l); } public void removePropertyChangeListener(PropertyChangeListener l) { propertyChangeListeners.remove(l); } protected void firePropertyChange(String name, Object oldValue, Object newValue) { PropertyChangeEvent evt = new PropertyChangeEvent(this, name, oldValue, newValue); for (PropertyChangeListener l : propertyChangeListeners) { l.propertyChange(evt); } } }
んでメインは下のように書き換える
package okazuki; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.logging.Level; import java.util.logging.Logger; import javax.beans.binding.Binding; import javax.beans.binding.BindingContext; import javax.beans.binding.BindingConverter; import javax.beans.binding.BindingValidator; import javax.beans.binding.ValidationResult; import javax.swing.JTextField; public class Main { public static void main(String[] args) { Person p = new Person(); JTextField text = new JTextField(); BindingContext ctx = new BindingContext(); // 誕生日とテキストフィールドのテキストのバインド Binding binding = ctx.addBinding(p, "${birthday}", text, "text"); binding.setConverter(new BindingConverter(){ private SimpleDateFormat fmt = new SimpleDateFormat("yyyy/MM/dd"); public Object sourceToTarget(Object source) { // nullは空文字 if (source == null) return ""; // あとはフォーマットする return fmt.format((Date) source); } @Override public Object targetToSource(Object target) { try { return fmt.parse((java.lang.String) target); } catch (ParseException ex) { return null; } } }); binding.setValidator(new BindingValidator(){ public ValidationResult validate(Binding binding, Object value) { // nullは無効なフォーマットが入力された証! if (value == null) { return new ValidationResult(ValidationResult.Action.SET_TARGET_FROM_SOURCE); } // nullじゃなければOK return null; } }); ctx.bind(); System.out.println("誕生日に今日の日付をセット"); p.setBirthday(new Date()); System.out.println("textField.text = " + text.getText()); System.out.println("p.birthday = " + p.getBirthday()); System.out.println("テキストフィールドにでたらめな値をセット"); text.setText("hoge"); binding.setSourceValueFromTargetValue(); System.out.println("textField.text = " + text.getText()); System.out.println("p.birthday = " + p.getBirthday()); System.out.println("誕生日にnullをセット"); p.setBirthday(null); System.out.println("textField.text = " + text.getText()); System.out.println("p.birthday = " + p.getBirthday()); } }
実行結果は下の通り
誕生日に今日の日付をセット textField.text = 2007/07/07 p.birthday = Sat Jul 07 14:39:45 JST 2007 テキストフィールドにでたらめな値をセット textField.text = 2007/07/07 p.birthday = Sat Jul 07 14:39:45 JST 2007 誕生日にnullをセット textField.text = p.birthday = null