scala.io.BufferedSourceクラスのバグの修正

ちょっと直してみた。(まだバグがあるかもしれないけど)
liftwebの日本語処理がおかしいときはお試しあれ。(自己責任でお願いします。。。)

Index: BufferedSource.scala
===================================================================
--- BufferedSource.scala	(revision 15559)
+++ BufferedSource.scala	(working copy)
@@ -60,6 +60,7 @@
   def fillBuffer() = {
     byteBuffer.compact()
     charBuffer.position(0)
+    charBuffer.limit(charBuffer.capacity);
     var num_bytes = byteChannel.read(byteBuffer)
     while (0 == num_bytes) {
       Thread.sleep(1);  // wait 1 ms for new data
@@ -65,10 +66,10 @@
       Thread.sleep(1);  // wait 1 ms for new data
       num_bytes = byteChannel.read(byteBuffer) 
     }
+    byteBuffer.flip()
     num_bytes match {
       case -1 => 
         endOfInput = true;
-        byteBuffer.position(0)
         decoder.decode(byteBuffer, charBuffer, true)
         decoder.flush(charBuffer)
       case num_bytes => 
@@ -73,10 +74,9 @@
         decoder.flush(charBuffer)
       case num_bytes => 
         endOfInput = false
-        byteBuffer.flip()
         decoder.decode(byteBuffer, charBuffer, false)
-        charBuffer.flip()
     }
+    charBuffer.flip();
   }
   override val iter = new Iterator[Char] {
     var buf_char = { 
@@ -86,12 +86,12 @@
     def hasNext = { charBuffer.remaining() > 0 || !endOfInput}
     def next = { 
       val c = buf_char
-      if (charBuffer.remaining() == 0) {
+      if (charBuffer.remaining() == 0 && !endOfInput) {
         fillBuffer()
       }
-      if (!endOfInput) {
-        buf_char = charBuffer.get()
-      }
+      if (charBuffer.remaining() >= 1) {
+	      buf_char = charBuffer.get()
+	  }
       c
     }
   }

Scalaのライブラリにバグ?

scala.io.BufferedSourceなのだが、入力ストリームの1文字が2バイト以上の時にうまくうごかないかも。
というのもdecodeした後のバッファと変換前のバッファを同じサイズで確保しているので、

  val byteBuffer = ByteBuffer.allocate(buf_size)
  var charBuffer = CharBuffer.allocate(buf_size)

decoder.decode(byteBuffer,rBuffer,se)
したときにbyteBufferに変換先に入りきらないデータが残ることになるのだが、
byteChannel.read(byteBuffer)
が-1を返したときにその辺の考慮が抜けててbyteBufferの中身が捨てられそう。
おかげでliftで日本語使ったときにパースエラーになるときがある。(場合によっては予期しない終端を考慮しない箇所とかあってOutOfMemoryError。ちょっとscala/liftのXMLパーザ周りも見てみたけど、作りが粗い気がする。)
結構致命的だと思うのだけどみんなどうしてるのだろう。そもそも英語圏以外でliftってあんまり使われてないのかな?
報告したほうが良いのだろうけど調べるので力尽きた。
(あとまだちゃんと確証取ったわけじゃない)

scala.io.BufferedSourceクラスのバグの検証コード

Scalaのライブラリにバグ? - 玲瓏庵
の検証コード。

    val buf = new StringBuilder;
    for(i <- 1 to 2000) {
      buf.append("あ");
    }
    for(i <- 1 to 2000) {
      buf.append("a");
    }
    buf.append("E");
    
    val s = Source.fromInputStream(new ByteArrayInputStream(buf.toString.getBytes("UTF-8")),"UTF-8");
    while(s.hasNext) {
      print(s.next);
    }

正常に動作しているならば最後に'E'って表示されるはず。
だけど'a'表示中に途切れる。
よくよく見ると昨日の説明は不足していて、詳しくはマルチバイト文字が来るとCharBufferの方はflipするとlimitが小さくなるのだが、ByteBufferの方はlimitが変わらないせいでその次にシングルバイト文字ばっかりくるとByteBufferにのこっちゃう。だけど、byteChannel.readに-1が来るとそこでByteBufferの残りを捨ててしまうって事だ。

つたない英語でscala-langに登録してみた。

MAC OS Xの外部ディスプレイ

今更WindowsからMACに乗り換えたのだが、やはり基本的なところの動きがすばらしいですな。例えば壁紙とかの画面の設定を外部ディスプレイ毎に覚えといてくれるのね。昔使ってたWinマシンなんぞ、スリープから復活させたときに外部ディスプレイが死んだりしてたのだが。

フォームの処理 - lift

http://liftweb.net/index.php/Hello_Darwin#my_first_form

より写経する。

フォームを作る。

helloFrom.html
<lift:surround with="default" at="content">
    <h1>Hello Form</h1>
    Hello <lift:HelloForm.who />
    <br/>
    <form>
        <label for="whoField">Who :</label>
        <input type="text" name="whoField"/>
        <input type="submit" value="send"/>
    </form>
</lift:surround>


lift:surroundは、
http://liftweb.net/index.php/LiftTags
によると、withでテンプレート名を指定し、src/main/webapp/templates-hidden/default.htmlに
テンプレートが置かれているはずであり、実際置いてあった。
at属性でに展開することを指定しているのかと予想できる。

default.html
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:lift="http://liftweb.net/">
	<head>
		<meta http-equiv="content-type" content="text/html; charset=UTF-8" />
		<meta name="description" content="" />
		<meta name="keywords" content="" />
		
		<title>sandbox.lift.hellodarwin:hellodarwin:1.0-SNAPSHOT</title>
		<script id="jquery" src="/classpath/jquery.js" type="text/javascript"></script>
	</head>
	<body>
		<lift:bind name="content" />
		<lift:Menu.builder />
		<lift:msgs/>
	</body>
</html>

snipet作る。

入力された値をのところに展開して表示するプログラムのようだ。

HelloForm.scala
package sandbox.lift.hellodarwin.snippet

import net.liftweb.http.S

class HelloForm {
  def who = <tt>{S.param("whoField").openOr("")}</tt>
}

net.liftweb.http.Sは、

http://scala-tools.org/mvnsites/liftweb/lift-webkit/scaladocs/net/liftweb/http/S$object.html
より、
現在のリクエストとレスポンスを示すオブジェクトのようだ。

S.paramはCan[String]を返すことになっている。
クラスCanは
http://scala-tools.org/mvnsites/liftweb/lift-webkit/scaladocs/net/liftweb/util/Can.html
に説明があり、空かStringのどちらかであることを示すようだ。
openOr("")は値があればそれを返し、無ければ引数の値を返すCanのメンバである。

Scalaでマネする。

与えられた木から、子→親への対応を作るという問題なのだが、これが簡単そうで実はなかなか奥が深そうだ。
いくつか回答を眺める中で、その中で、与えられた木から、子→親への対応を作る - MEMO:はてな支店が良く出来ていたので、練習がてらScalaで真似してみる。

object Tree {
  def main(args : Array[String]) : Unit = {
    var tree = ('root,('RClavicle,('RUpperArm,('RLowerArm,('RHand,Nil)::Nil)::Nil)::Nil)
                ::('LClavicle, ('LUpperArm, ('LLowerArm, ('LHand,Nil)::Nil)::Nil)::Nil)
                ::('RHip, ('RUpperLeg, ('RLowerLeg, ('RFoot,Nil)::Nil)::Nil)::Nil)
                ::('LHip, ('LUpperLeg, ('LLowerLeg, ('LFoot,Nil)::Nil)::Nil)::Nil)
                ::Nil);
      println(f(tree));
  }

  def f(tree:(Symbol,List[_])) : List[(Symbol,Symbol)] = tree match {
  case (_,Nil) => Nil;
  case (p,(c:Symbol,cs:List[_])::ps) => List((c,p))++f((c,cs))++f((p,ps));
  }
}

結果

List(('RClavicle,'root), ('RUpperArm,'RClavicle), ('RLowerArm,'RUpperArm), ('RHand,'RLowerArm), ('LClavicle,'root), ('LUpperArm,'LClavicle), ('LLowerArm,'LUpperArm), ('LHand,'LLowerArm), ('RHip,'root), ('RUpperLeg,'RHip), ('RLowerLeg,'RUpperLeg), ('RFoot,'RLowerLeg), ('LHip,'root), ('LUpperLeg,'LHip), ('LLowerLeg,'LUpperLeg), ('LFoot,'LLowerLeg))

最初の木の定義がやぼったい事に、scalaだと普通どうかくのかな。おしえてエロイ人><