この日記は私的なものであり所属会社の見解とは無関係です。 GitHub: takahashikzn

"Selecting From Collections"式とLambda式の怪


(2009/08/25修正: Enumだけの問題ではなく普通のBeanでも現象が発生したため、文言を若干修正しました)


OGNLのSelecting From Collections式(以下、SFC式)は、コレクションから条件にマッチする要素だけを抽出する式です。
みんな大好き内部イテレータってヤツですね。


ところがこのSFC式、引数のプロパティをSFC式の内部で使用する場合には、奇妙な動作をする場合があります。
なお、動作環境はStruts-2.1.6アンドOGNL-2.7.3です。


次のような例な考えてみましょう。
47都道府県を表すEnumのリストから、指定したキー名を持つものだけを抽出する例です。


Enumクラスはこんな感じ。何の変哲もないEnumクラスです。

public enum Pref {
    HOKKAIDO, AOMORI, ..., OKINAWA;
}

外部イテレータでやってみる

まずは外部イテレータでやってみます。

<%-- Pref列挙型の値の一覧。型で言うと List<Pref> 相当 --%>
<s:set var="#prefs" value="..." />

<%-- 選別する都道府県のキー名 --%>
<s:set var="#selectionPrefKeys" value="{ 'TOKYO', 'OSAKA', 'KYOTO' }" />

<s:set var="#selectedPrefs" value="{}" />

<s:iterator var="pref" value="#prefs">
  <s:if test="#selectionPrefKeys.contains(#pref.name())">
    <s:set value="#selectedPrefs.add(#pref)" />
  </s:if>
</s:iterator>

<s:property value="#selectedPrefs" />


実行結果は次の通りです。

{ TOKYO, OSAKA, KYOTO }

内部イテレータでやってみる

つぎに、この例をSFC式を使って書き直してみましょう。

<%-- Pref列挙型の値の一覧。型で言うと List<Pref> 相当 --%>
<s:set var="#prefs" value="..." />

<%-- 選別する都道府県のキー名 --%>
<s:set var="#selectionPrefKeys" value="{ 'TOKYO', 'OSAKA', 'KYOTO' }" />

<%-- #selectionPrefKeysがキー名を含むEnum値のみ選択する --%>
<s:set var="selectedPrefs" value="#prefs.{? #selectionPrefKeys.contains(#this.name()) }" />

<s:property value="#selectedPrefs" />


実行結果は次の通りです。

{}

んん???空のリストが表示されていますね。
というわけで、原因は調べていませんが、動きません。

それでも内部イテレータじゃなきゃヤダモン!!


これも理由は調べていませんが、#selectionPrefKeys.containsに直接Enum値を渡すのではなく、ラムダ式を経由するとSFC式でも動きます。

<%-- 都道府県を表すEnumのリスト。(HOKKAIDO, AOMORI, ..., OKINAWA) --%>
<s:set var="#prefs" value="..." />

<%-- 選別する都道府県のキー名 --%>
<s:set var="#selectionPrefKeys" value="{ 'TOKYO', 'OSAKA', 'KYOTO' }" />

<%-- ラムダ式を経由している --%>
<s:set var="selectedPrefs" value="#prefs.{? (:[#selectionPrefKeys.contains(#this.name())])(#this) }" />
<%-- これでもOK --%>
<%-- <s:set var="selectedPrefs" value="#prefs.{? (:[#selectionPrefKeys.contains(#this)])(#this.name()) }" /> --%>

<s:property value="#selectedPrefs" />


実行結果は次の通りです。

{ TOKYO, OSAKA, KYOTO }

ふーむ、なんでなんだろう。まあOGNLのソースを読めばわかるのでしょうが。
ぶっちゃけOGNLのソースは少しアレなので、読むモチベーションが不足気味な今日この頃。