ADF Table とデータコントロールの選択状態同期
だんだん内容がショボくなって参ります。
データコントロールツリーからコレクションをデザイナ上にドラッグ・ドロップしてTableを構成するとき、行選択を有効にしておくと勝手に選択状態が同期するようになるのですが、一度行選択を無効にして構成してしまうと、後で RowSelection 属性の値だけ変えても、見かけ上は行選択できるようになりますが、データコントロールまで反映されません。
で、毎回「どの属性設定するんだっけ…」と悩むことになるので、ここにメモしときます。
選択状態の同期は、af:table 要素の以下の2つの属性にEL式を設定します。
- selectionListener : #{bindings.(DC名).collectionModel.makeCurrent}
- selectedRowKeys : #{bindings.(DC名).collectionModel.selectedRow}
selectionListener属性で、Tableの選択状態の変更を拾ってデータコントロールへつなげ、selectedRowKeysでデータコントロールの選択状態をTableに反映する、という感じ。
あと、アクションメソッド内で「選択された行の項目Nを取得したい」なんてときは、無理にRichTableオブジェクトごゴニョゴニョするよりは、同期するデータコントロールの取得したい項目を個別にバインドしておいて、EL式#{bindings.(項目名).inputValue}などで取ったほうが楽です。
参考:
http://otndnld.oracle.co.jp/document/products/jdev/11/doc_cd/web.1111/B52028-01/web_tables_forms.htm
PagePhaseListener のメモ
ADFには、JSFのPhaseEventとは別に、ページライフサイクルの各フェーズの開始・終了で発火するPagePhaseEventというのがあります。
こいつのリスナ、PagePhaseListener は、呼ばれるフェーズを選択できず、毎フェーズの開始/終了で必ず呼ばれます(JSFでいうところの「ANY_PHASE」)。ページ生成時に一回だけ呼ばれる前提でリソースを確保すると、えらいことになるので注意。
フェーズの順序は、開発者ガイドの以下のページで説明されています。
http://otndnld.oracle.co.jp/document/products/jdev/11/doc_cd/web.1111/B52028-01/adf_lifecycle.htm
…とまあここまで書いておいてなんですが、大抵の用途ではJSFのPhaseListenerだけで事足りると思います(というか、PagePhaseListenerのアドバンテージが今ひとつ思い浮かばない)。
それから、JSFでも同様ですが、複数コンポーネントの書き換えが同時に発生する場合は、ひとつのスレッドで上記のライフサイクルが複数回回ったりするのでこれも注意。
DB接続のようなリソースを、1リクエストの中で確保して解放、というような時は、ここではなく、素直にServletFilterとして組み込んだほうが安全ですね。
PhaseEvent, PagePhaseEvent をそれぞれ拾って、フェーズ名をログに出力するコードを置いときます。両方組み込んで実行すると、JSF,ADFの各フェーズがどういう絡みになっているか、なんとなく読み解けるかも。
https://github.com/shout-poor/Smallcodes/tree/master/study_adf
表で合計行の表示
最近 Oracle ADF を使ってます。古き良きC/S時代のVisualBasicみたいなデータバインディング付きのUIコンポーネントなんかがあったり、コンポーネントをIDEのデザイナ上でペタペタ貼って画面を作ったりと、おじさんにはすんなり理解できそうな感じがよいです。まあ、JDeveloperが生成するXMLの山や、「動かしてみないとわからない」感が非常に危なっかしいという面もありますが。
ただ、テキトーに書くとうまく動いてくれず、情報も少ないので、ちょっとしたことでも実装方法を探すのに余計な時間を食わされるのが辛いところ。
ということで、しょーもないプラクティスをメモ代わりに書いていこうという次第。
で、一回目は、データバインディングした表(af:table)に合計行を表示する手順。VBのグリッドコントロールならプロパティ一発なんですけどもねえ。
- データ元となる View Object に属性を追加する。属性の編集ダイアログ「ビュー属性」で、「値のタイプ」を「式」にし、「object.getRowSet().sum("(集計したい属性名)")」を設定。ここの集計関数は、sum 以外に count, avg, min, max が標準で用意されてる模様。
- 表示するページのデータバインディング定義を開き、1で追加した属性をバインディングに追加する。
- 表示するページのデザイナを開き、ADF表の集計したいカラムを選択し、右クリック→「ファセット - Column」→「Footer」をクリック。すると、フッタ行が表示され、選択したカラムの下にFooterファセットが追加される。
- Footerファセットに出力テキスト(af:outputText)を追加し、value属性にEL式「#{binding.(1で追加した属性名).inputValue}」を設定する。
この際、 データコントロールから属性をドラッグ&ドロップすると、value属性が「#{row.(属性名)}」となり、フッタでは row が取れないのでうまく表示されない。
以上。
Footerファセットにはalignの設定ができないので、右寄せにしたい場合は出力テキストをJSFコンポーネントのPanel Grid(h:panelgrid)でラップして、Grid Panelのstyle属性にtext-align:right;を追加してあげましょう。
また、集計元のカラムが編集可能で、編集したら即座に再計算したい場合は、4 で追加した出力テキストのPartialTriggers属性に、集計元フィールドのidを設定してあげるとよいです。
ちなみに、View Object の属性の初期値欄に入力する式は、Oracleのドキュメントで「Groovy Expression」と説明されていて、Groovyの書式に従って解釈されるようですよ。
別解としては、集計関数込みのSQLで新たな View Object を定義して、元のView Objectとリンクするという方法も。グルーピング単位が固定になってしまうのと、DBにコミットするまで再計算されなくなりますが、件数が多い場合はこっちのほうがパフォーマンスはよいかと思います。
Scala ARM的な自動リソース管理(超簡易版)
自動リソース管理(というかclose管理)を使いたいけど、Scala ARMライブラリを導入するほどのもんでもないなー、というときに自作するためのメモ。
// ラッパクラスと関数の定義 class ManagedResource[T <: {def close(): Unit}](resource: T) { def foreach[U](f: T => U) { try { f(resource) } finally { resource.close } } } def managed[T <: {def close(): Unit}](resource: T) = new ManagedResource(resource) // 使用例 for ( rd <- managed(new LineNumberReader(new FileReader(inFileName))); wri <- managed(new PrintWriter(new FileWriter(outFileName))) ) { def read(l: List[String]=Nil): List[String] = { Option(rd.readLine) match { case Some(s): read(s::l) case None: l.reverse } } val inList = read() val outList = hogehoge(inList) // 諸々処理 for(s <= outList) wri.println(s) } // スコープから抜けるとrd, wriはクローズされる }
FibBuzzをJavaで書いてみる
FizzBuzz は時代遅れらしいので FibBuzz を発案します | cod.note
Javaで書いて見ました。CodeGolf的に頑張るのはJavaっぽくない(偏見)のでまあ普通に。
longだとフィボナッチ数列がn=92で桁あふれしてしまうのでBigIntegerでやってます。このため非常に見づらいです。
import java.math.BigInteger; import static java.math.BigInteger.*; public class FibBuzz { public static void main(String[] args) { final long COUNT = 100l; final BigInteger THREE = BigInteger.valueOf(3l); final BigInteger FIVE = BigInteger.valueOf(5l); BigInteger[] fibPrev = new BigInteger[] { ZERO, ONE }; for (long n = 1l; n <= COUNT; n++) { final BigInteger fib; if (n <= 1l) { fib = BigInteger.valueOf(n); } else { fib = fibPrev[0].add(fibPrev[1]); fibPrev[0] = fibPrev[1]; fibPrev[1] = fib; } String output = (fib.mod(THREE).equals(ZERO) ? "Fizz" : "") + (fib.mod(FIVE).equals(ZERO) ? "Buzz" : ""); System.out.println("".equals(output) ? String.valueOf(fib) : output); } } }
ところでn=0は含まなくていいのかしら。FizzBuzzだとn>=1ですけど。
Scalaで「アラビア数字・ローマ数字変換」
お題はこちら。
お題:アラビア数字・ローマ数字変換 - No Programming, No Life
テストは割愛。
ご覧のとおり、
- アラビア数字→ローマ数字の場合は、大きい単位から順に元の数字から引いていって、引けた記号を積むだけ
- ローマ数字→アラビア数字の場合は、左側から大きい順に並んでいると仮定して、その分の数字を加算していくだけ
という単純なもの。減算則はについては、該当する数字(900とか400とか)を二文字で一つの記号として扱うことで解決。
ようやく再帰を使ったコーディングにも慣れてきたけど、あんまり面白いコードは思いつかないなあ。
object ArabicRoman { val codeTable = List( (1000, "M"), (900, "CM"), (500, "D"), (400, "CD"), (100, "C"), (90, "XC"), (50, "L"), (40, "XL"), (10, "X"), (9, "IX"), (5, "V"), (4, "IV"), (1, "I")) def arabicToRoman(src: Int): String = { require((src >= 1) && (src <= 3999)) def convert(left: Int, cont: String = "", code: List[(Int, String)] = codeTable): String = { val (unitVal, unitChar) = code.head left - unitVal match { case n if (n == 0) => cont + unitChar case n if (n > 0) => convert(n, cont + unitChar, code) case _ => convert(left, cont, code.tail) } } convert(src) } def romanToArabic(src: String): Int = { require(src != null && src.nonEmpty) val p = src.toUpperCase() require("""[^MDCLXVI]""".r.findFirstMatchIn(p) == None) def convert(left: String, cont: Int = 0, code: List[(Int, String)] = codeTable): Int = { val (unitVal, unitChar) = code.head left.splitAt(unitChar.length) match { case ("", _) => cont case (`unitChar`, tail) => convert(tail, cont + unitVal, code) case _ => convert(left, cont, code.tail) } } convert(p) } }
(8/24 追記)
もしかして、引数の領域が定められていて、領域外の場合はエラー、みたいな場合は、アサーションにかけるよりPartialFunctionにすべきなのかも。
ということでPartialFunction版。ガードが長いとちと汚い。
object ArabicRoman { type =?>[A, B] = PartialFunction[A, B] val codeTable = List( (1000, "M"), (900, "CM"), (500, "D"), (400, "CD"), (100, "C"), (90, "XC"), (50, "L"), (40, "XL"), (10, "X"), (9, "IX"), (5, "V"), (4, "IV"), (1, "I")) val arabicToRoman: (Int) =?> String = { case src if (src >= 1 && src <= 3999) => { def convert(left: Int, cont: String = "", code: List[(Int, String)] = codeTable): String = { val (unitVal, unitChar) = code.head left - unitVal match { case n if (n == 0) => cont + unitChar case n if (n > 0) => convert(n, cont + unitChar, code) case _ => convert(left, cont, code.tail) } } convert(src) } } val romanToArabic: (String) =?> Int = { case src if (Option(src).exists{s => {s.nonEmpty && ("""[^MDCLXVI]""".r.findFirstMatchIn(s.toUpperCase) == None)}}) => { def convert(left: String, cont: Int = 0, code: List[(Int, String)] = codeTable): Int = { val (unitVal, unitChar) = code.head left.splitAt(unitChar.length) match { case ("", _) => cont case (`unitChar`, tail) => convert(tail, cont + unitVal, code) case _ => convert(left, cont, code.tail) } } convert(src.toUpperCase()) } } }
Scalaで「フルパスから相対パスを求める」
懲りずにやってみた。
お題はこちら。
お題:フルパスから相対パスを求める - No Programming, No Life
お題とは直接関係ないけど、例外のテストを行うための@Test(expected=classOf[HogeHoge]) をうまく略記する方法はないものでしょうか。
※仕様が追加されていたので修正しました
object RelativePath { val sepChar = '/' def getRelativePath(base: String, target: String) : String = { // 不正文字チェック def check(p: String) = { require(p != null, "Parameter must not be null") require(p.nonEmpty, "Parameter must not be empty") require(""".*[\\\?\*\:\"\|\>\<].*|.*\.$""".r.findFirstIn(p) == None, "Bad character") require(p.startsWith(sepChar.toString()), "Parameter must start with Slash") } check(base); check(target) // 末端ノードの整理 val l1 = (base.split(sepChar).filter(_.nonEmpty).toList, base.last) match { case (x, `sepChar`) => x case (x, _) => x.init } val l2 = (target.split(sepChar).filter(_.nonEmpty) ++ Array(target.last).collect{case `sepChar` => ""}).toList // 共通する上位パスを除去(分岐ノードまで降りてくる) def removeSame(l1: List[String], l2:List[String]): (List[String], List[String]) = l1 match { case hl1::tl1 if (hl1 == l2.head) => removeSame(tl1, l2.tail) case _ => (l1, l2) } removeSame(l1, l2) match { // 同レベルディレクトリ case (Nil, sl2) => sl2.mkString("./",sepChar.toString,"") // 分岐ノードまでの段数分「../」の生成 case (sl1, sl2) => (sl1.map(_ => "..") ++ sl2).mkString("",sepChar.toString,"") } } } /** 以下テスト */ class RelativePathTest { import org.junit.Assert._ import org.junit.Test import RelativePath._ @Test def testGetRelativePath = { def as(p1: String, p2: String, to: String) = assertEquals(to, getRelativePath(p1, p2)) as("/aaa/bbb/from.txt", "/aaa/bbb/to.txt", "./to.txt") as("/aaa/bbb/from.txt", "/aaa/to.txt", "../to.txt") as("/aaa/bbb/from.txt", "/aaa/bbb/ccc/to.txt", "./ccc/to.txt") as("/aaa/bbb/from.txt", "/aaa/ccc/ddd/to.txt", "../ccc/ddd/to.txt") as("/aaa/bbb/from.txt", "/ddd/ccc/to.txt", "../../ddd/ccc/to.txt") as("/aaa/bbb/", "/aaa/ddd/to", "../ddd/to") as("/aaa/bbb/", "/aaa/ccc/", "../ccc/") as("/aaa/bbb/ccc.txt", "/aaa/bbb/ccc.txt", "./ccc.txt") as("/aaa/", "/aaa/", "./") as("//aaa///bbb////from.txt", "/////aaa//////bbb///////to.txt", "./to.txt") as("//aaa///bbb///fff//from.txt", "/////aaa//////bbb///////to.txt", "../to.txt") } private def tryEx(p1: String, p2: String) : Unit= { getRelativePath(p1, p2); Unit } @Test(expected=classOf[IllegalArgumentException]) def testEmpty1 = tryEx("", "/bbb/to.txt") @Test(expected=classOf[IllegalArgumentException]) def testEmpty2 = tryEx("/bbb/to.txt", "") @Test(expected=classOf[IllegalArgumentException]) def testNull1 = tryEx(null, "/bbb/to.txt") @Test(expected=classOf[IllegalArgumentException]) def testNull2 = tryEx("/bbb/to.txt", null) @Test(expected=classOf[IllegalArgumentException]) def testStartNotSlash1 = tryEx("bbb/to.txt", "/bbb/to.txt") @Test(expected=classOf[IllegalArgumentException]) def testStartNotSlash2 = tryEx("/bbb/to.txt", "bbb/to.txt") @Test(expected=classOf[IllegalArgumentException]) def testInvChar11 = tryEx("/b\\b", "/bbb/to.txt") // 以下省略(不正文字チェックのテストを対象文字1つずつ) }