JSF2.0の選択タグについて

JSF2.0では選択タグ利用時のCollectionの型決定について、少しメモ程度に触れたいと思います。

超シンプルなManagedBeanをまずは以下のように作成します。

import java.io.Serializable;
import java.util.Set;

import javax.enterprise.context.SessionScoped;
import javax.faces.model.SelectItem;
import javax.inject.Named;

@Named("registrationBean")
@SessionScoped
public class RegistrationBean implements Serializable {

    private static SelectItem[] computerLanguages;
    
    private Set<String> selectedComputerLanguages;
    
    static {
        computerLanguages = new SelectItem[]{
                new SelectItem("C", "C"),
                new SelectItem("C++", "C++"),
                new SelectItem("Java", "Java"),
                new SelectItem("Ruby", "Ruby"),
                new SelectItem("Perl", "Perl"),
                new SelectItem("PHP", "PHP"),
                new SelectItem("Go", "Go"),
        };        
    }

    public SelectItem[] getComputerLanguages() {
        return computerLanguages;
    }

    public Set<String> getSelectedComputerLanguages() {
        return selectedComputerLanguages;
    }

    public void setselectedComputerLanguages(Set<String> selectedComputerLanguages) {
        this.selectedComputerLanguages = selectedComputerLanguages;
    }
}

で、入力画面はとりあえずこれまで使用したことのある言語をリスト形式で入力できるような感じにします。

【skillRegistrationForm.xhtml

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:ui="http://java.sun.com/jsf/facelets">

	<h:head>
        <title>Skill Registration</title>
    </h:head>
    
    <h:body>
        <h:form>
            <h:panelGrid columns="2">
                これまで利用した言語
                <h:selectManyListbox value="#{registrationBean.selectedComputerLanguages}">
                    <f:selectItems value="#{registrationBean.computerLanguages}"/>
                </h:selectManyListbox>
            </h:panelGrid>
            <h:commandButton value="登録" action="showRegisteredInformation" />
        </h:form>
    </h:body>
</html>

確認画面は入力情報をただ表示するだけなのでこちらもシンプルに以下のようにします。

【showRegisteredInformation.xhtml

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:ui="http://java.sun.com/jsf/facelets">

	<h:head>
        <title>Comfirm your skills</title>
    </h:head>
    
    <h:body>
        <h:panelGrid columns="2">
            利用した言語
            <h:outputText value="#{registrationBean.selectedComputerLanguages}" />
        </h:panelGrid>
    </h:body>
</html>

このプログラムを実行し、C++Java、Goを選択して登録ボタンを押下すると、howRegisteredInformation.xhtmlに以下のように表示されます。

利用した言語 [C++, Go, Java]

ここに、JSF2.0の選択タグ利用時のCollectionの型決定メカニズムが潜んでいます。

skillRegistrationForm.xhtmlでは複数選択可能なリストにRegistrationBeanプロパティのcomputerLanguages配列の内容を表示しています。これは配列なのでリスト内に順番に表示されます。

しかし、結果は、C++の次にGoが表示されています。

JSF2.0ではこのコレクション利用時の型決定メカニズムが明確に定義されており、結論から言うこの出力順はTreeSetと同じになります。

public class Main {

    public static void main(String[] args) {
        Set<String> sets = new TreeSet<String>();
        sets.add("Java");
        sets.add("C++");
        sets.add("Go");
        System.out.println(sets.toString());
    }

}

試しに、上記プログラムをスタンドアロンで実行してみると全く同じ結果が得られます。

では、選択リストと同じ順番で表示するにどうすればいいかということになるのですが、これにはいくつか方法があります。

1つは、RegistrationBeanで定義したselectedComputerLanguagesを次のようにします。

@Named("registrationBean")
@SessionScoped
public class RegistrationBean implements Serializable {
    ...
    private Set<String> selectedComputerLanguages = new LinkedHashSet<String>();
    ...
}

これとは別にh:selectManyListboxタグのcollectionTypeに型を明示することも可能です。

<h:selectManyListbox value="#{registrationBean.selectedComputerLanguages}" collectionType="java.util.LinkedHashSet">
    ...
</h:selectManyListbox>

いずれの場合も結果は”利用した言語 [C++, Java, Go]”と表示されるはずです。

では両方設定するとどうなるのかというと、試しに、RegistrationBeanのselectedComputerLanguagesプロパティはLinkedHashSetのままでh:selectManyListboxタグのcollectionTypeにTreeSetを設定してみてください。

実行結果は最初の実行結果と同じ”利用した言語 [C++, Go, Java]”が表示されるはずです。

つまり、JSF2.0では簡単に説明すると次のような処理が行われています。
(サンプルプログラム内でSet型を利用しているので、説明も分かりやすくするためにSet型を用いて説明します。ちなみにリスト型の場合は特に説明は不要でしょう。)

  • collectionTypeの定義があればそれに定義されている型を利用する。
  • なければBeanのプロパティ値を取得し、その型でデータを設定する。
  • 上記処理時に、もしプロパティ値がnullならば、TreeSetを型として生成する。

これが、コレクションの型決定メカニズムのようですが、ここで1つ、collectionTypeを指定せずに、selectedComputerLanguagesの型をSortedSetにして実行するとどうなるでしょうか。

結論から言うと、結果はSet型のときと同じになるようです。

ここが疑問なのですが、TreeSetはSortedSetインターフェースを実装しているからわかるんだけど、Set型ならHashSetで生成されてもいいような気がします。
この辺がいまいちコードを解析したわけじゃないから不明です。

collectionTypeを設定しなかった場合の動きについてはもう少し調査が必要かな。

じっくりソースコードを眺める機会があれば調べてみたいところだけど、この辺はもしかして実装依存