Codec.Encryption.RSAの使い方(しかし多分Codec.Crypto.RSAを使うほうが良い)

Haskell勉強中

「すごいHaskell楽しく学ぼう」でHaskellをひと通り勉強した。
この本読みやすくて面白いですよ。

すごいHaskellたのしく学ぼう!

すごいHaskellたのしく学ぼう!

Codec.Encryption.RSA

色々Haskellで面白そうなことを見つけては遊んでいるのだが、"Codec.Encryption.RSA"の用法について日本語の情報がなかったので、解明しつつ記事にまとめようかと思う。
多分Haskell上のRSA暗号の実装です。
RSA暗号は、公開鍵暗号方式の一つで、ネットワーク上の通信の暗号化やデジタル署名などに広く用いられています。
詳しくは→wikipedia:RSA暗号

導入

"Codec.Encryption.RSA"はパッケージ名"crypto"に入っているので、Haskell Platformが入っている環境でなら下記コマンドでインストールできる。

$ cabal install crypto

用法

hackage上の情報だとこれのみ→http://hackage.haskell.org/packages/archive/Crypto/4.2.0/doc/html/Codec-Encryption-RSA.html
「簡単に使えるよ!詳しくはここ見てね!→リンク」っていう部分がすべてリンク切れなので、どうしたものか…。

"Codec.Encryption.RSA"の関数は下記の2つ。

encrypt :: ([Octet], [Octet]) -> [Octet] -> [Octet]Source
decrypt :: ([Octet], [Octet]) -> [Octet] -> [Octet]Source

OctetはWord8の別名なので、ByteStringをunpackして[Octet]が作れる。

RSA129

RSA129というRSAの解読の懸賞問題です。参考→http://www.mew.org/~kazu/doc/rsa.html
なお今回は文字列に変換する部分は省略。暗号化、復号化した数値が一致する事を確認するのみにする。
ライブラリの使用法の確認に目的を絞る!

この問題の公開鍵と、後に解読された秘密鍵が下記の通り。

n = 114381625757888867669235779976146612010218296721242362562561842935706935245733897830597123563958705058989075147599290026879543541
e = 9007
d = 106698614368578024442868771328920154780709906633937862801226224496631063125911774470873340168597462306553968544513277109053606095

公開鍵は(n, e)で、秘密鍵は(n, d)。
また、問題に対する回答が存在することを証明する為に掲載された署名がこちら。

署名
sign = 16717861150380844246015271389168398245436901032358311217835038446929062655448792237114490509578608655662496577974840004057020373
復号された署名
sign' = 06091819200019151222051800230914190015140500082114041805040004151212011819

署名は"sign'"を出題者のみが持っている秘密鍵で暗号化したもので、公開鍵で復号化できます。
[平文署名(sign')] - [秘密鍵] -> [暗号化署名(sign)]
[暗号化署名(sign)] - [公開鍵] -> [平文署名(sign')]
秘密鍵を持っている人のみが暗号化した署名(sign)を作れるので、署名として活用されます。

また、問題として出題された暗号文と復号化した解がこちら。

暗号文
message = 96869613754622061477140922254355882905759991124574319874695120930816298225145708356931476622883989628013391990551829945157815154
復号化された暗号文
message' = 200805001301070903002315180419000118050019172105011309190800151919090618010705

[平文(message')] - [公開鍵] -> [暗号文(message)]
[暗号文(message)] - [秘密鍵] -> [平文(message')]
公開鍵で誰でも暗号文(message)を作れますが、秘密鍵を持った人しか平文を得る事ができません。

Haskellでこれらの数値はIntegerとして扱えますが、今回使用するライブラリでは、[Octet]に変換しておかないといけません。
各数値を8bitで区切り[Octet]にして各々の名前に束縛します。

import Codec.Utils
n = i2osp 8 114381625757888867669235779976146612010218296721242362562561842935706935245733897830597123563958705058989075147599290026879543541
e = i2osp 8 9007
d = i2osp 8 106698614368578024442868771328920154780709906633937862801226224496631063125911774470873340168597462306553968544513277109053606095

-- 署名
sign = i2osp 8 16717861150380844246015271389168398245436901032358311217835038446929062655448792237114490509578608655662496577974840004057020373
-- 復号された署名
sign' = i2osp 8 06091819200019151222051800230914190015140500082114041805040004151212011819

-- 暗号文
message = i2osp 8 96869613754622061477140922254355882905759991124574319874695120930816298225145708356931476622883989628013391990551829945157815154
-- 復号化された暗号文
message' = i2osp 8 200805001301070903002315180419000118050019172105011309190800151919090618010705

上記コードをファイルに保存してghciでロードして確認を行います。

署名の復号化
*Main> import qualified Codec.Encryption.RSA as RSA
*Main RSA> fromTwosComp (RSA.decrypt (n,e) sign) == fromTwosComp sign'
True

[暗号化署名(sign)] - [公開鍵] -> [平文署名(sign')]
署名を公開鍵で復号した結果と予め知っている復号後の署名と一致しました。

署名の暗号化
*Main> import qualified Codec.Encryption.RSA as RSA
*Main RSA> fromTwosComp (RSA.encrypt (n,d) sign') == fromTwosComp sign
True

[平文署名(sign')] - [秘密鍵] -> [暗号化署名(sign)]
復号後の署名を暗号化すると、署名が得られます。

暗号文の復号化
*Main> import qualified Codec.Encryption.RSA as RSA
*Main RSA> fromTwosComp (RSA.decrypt (n,d) message) == fromTwosComp message'
True

[暗号文(message)] - [秘密鍵] -> [平文(message')]

平文の暗号化
*Main> import qualified Codec.Encryption.RSA as RSA
*Main RSA> fromTwosComp (RSA.encrypt (n,e) message') == fromTwosComp message
True

[平文(message')] - [公開鍵] -> [暗号文(message)]

結論

引数がすべて[Octet]で何を与えたらいいか分からない上に、
どうやって鍵を生成すればいいかも、HackageDB上の情報を読んだ感じではわかりませんでした。
RSA Packageの中の"Codec.Crypto.RSA"を使ったほうがよさげ。

基礎からsbtとScalaでServletプログラミング No.2

やりたいこと

jQueryインタラクティブに結果を変化させたい。
今回は前回作った基礎構成をベースに、HTMLのformに入力した値を即送信して、結果を表示するプログラムを作成する。

  • 数字をフォームに入力
  • 入力する度に内容を送信し、下記結果を受信し計算結果として表示する(入力した数値をNとする)

できたもの

今回からちゃんとバージョン管理して、記事書くごとにtag打ってリンク貼ろうと思う。
https://github.com/tomingtoming/ServletPractice/tree/ServletFromBeginning2

jQueryServletを呼び出すHTML部分

File:./src/main/webapp/index.html

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<script>
$(function() {
  $("#input input").keyup(function() {
    $.get("/fac",{ n : $(this).val() },function(data) {
      $(".factorial").text("factorial:"+data);
    });
    $.get("/fib",{ n : $(this).val() },function(data) {
      $(".fibonacci").text("fibonacci:"+data);
    });
  });
});
</script>
</head>
<body>
  <form id="input">
    input number here to start calculation
    <input type="text" /><span />
  </form>
  <ul>
    <li class="factorial">factorial:</li>
    <li class="fibonacci">fibonacci:</li>
  </ul>
</body>
</html>

これがファイル全体。

jQuery読込

ヘッダの中で下記の様に記述して、jQueryを読み込ませる。

<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>

これ以降の部分であればjQueryが使用できる。

サーバへの送受信実行部分

フォームへの入力が行われる度に、サーバに内容を送信して結果を表示させる部分

<script>
$(function() {
  $("#input input").keyup(function() {
    $.get("/fac",{ n : $(this).val() },function(data) {
      $(".factorial").text("factorial:"+data);
    });
    $.get("/fib",{ n : $(this).val() },function(data) {
      $(".fibonacci").text("fibonacci:"+data);
    });
  });
});
</script>

ここでは"#input input"によりid="input"のタグの子要素のinputタグを指定して、"keyup"イベントを取得して引数の関数を実行するよう定義している。
実行する関数の中では、"/fac"と"/fib"に各々フォームに入力された値をパラメータとして加えて送信し、結果を処理するコールバック関数に渡している。
コールバック関数の中では、".factorial"と".fibonacci"により、class="factorial"とclass="fibonacci"の属性を持ったタグを指定し、その内部のテキストを名称と結果で書き換えている。

入力フォーム
  <form id="input">
    input number here to start calculation
    <input type="text" /><span />
  </form>

formにidを付与してあり、これを上記スクリプトで指定して入力値を取得。

結果出力先
  <ul>
    <li class="factorial">factorial:</li>
    <li class="fibonacci">fibonacci:</li>
  </ul>

上記の部分で階乗とフィボナッチ用の各表示領域を準備しておき、上記スクリプトによる書き換え対象とする。

Servlet部分

共通trait

階乗を返すServletフィボナッチ数列を返すServletも数値を受け取って数値を返すので、共通のtraitを作成。
File:./src/main/scala/SimpleNumberApiServlet.scala

import javax.servlet.http.{HttpServlet, HttpServletRequest, HttpServletResponse}

trait SimpleNumberApiServlet extends HttpServlet {
  def response(req:BigInt):Option[BigInt];
  override def doGet(req:HttpServletRequest, res:HttpServletResponse) {
    println(req.getParameter("n"))
    res.setContentType("text/plain")
    res.setCharacterEncoding("UTF-8")
    val writer = res.getWriter()
    try {
      response(req.getParameter("n").toInt) match {
        case Some(n) => writer.write(n.toString)
        case None => "Calculation failed:" + req.getParameter("n")
      }
    } catch {
      case e:NumberFormatException =>
        writer.write("Illigal number format:" + req.getParameter("n"))
      case e =>
        writer.write("Error:" + req.getParameter("n"))
        println("Error:" + e)
    }
  }
}

リクエスト"/hoge?n=NNN"で来たnのパラメータを(数値が大きな場合に備えてBigIntで)数値にして、responseメソッドに渡して結果を文字列で返す。
クライアント側にはエラーが発生した場合にもエラーメッセージを単なる結果として返しているので、あまりよろしくはない…。
今回はjQueryからリクエストを投げ、結果をそのままテキストとして表示したかった為、ContentTypeを"text/plain"としてそのままテキストとして扱えるようにした。

    res.setContentType("text/plain")
    res.setCharacterEncoding("UTF-8")

また、trait継承クラスのresponseメソッドが入力したBigIntに結果を返せない可能性を考え、返り値をOption[BigInt]とした。

  def response(req:BigInt):Option[BigInt];
階乗を返すServlet

File:./src/main/scala/Factorial.scala

class Factorial extends SimpleNumberApiServlet {
  override def response(req:BigInt):Option[BigInt] = try {
    if(req.isValidInt) Option(factorial(req.toInt)) else None
  } catch {
    case _ => None
  }
  object factorial {
    private val stream = 0 #:: Stream.from(1).map(BigInt(_)).scanLeft(BigInt(1))(_*_).tail
    def apply(n:Int):BigInt = stream(n)
  }
}

object factorialは

  • Nの階乗のStream[BigInt]を保持している
  • apply(n:Int)によりn番目の数値を返す

上記の通りとなっている。大きい数字で呼ばれる程にメモリを食いつぶすので実用には耐えない。

フィボナッチ数列上のN番目の数値を返すServlet

File:./src/main/scala/Fibonacci.scala

class Fibonacci extends SimpleNumberApiServlet {
  override def response(req:BigInt):Option[BigInt] = try {
    if(req.isValidInt) Option(fibonacci(req.toInt)) else None
  } catch {
    case _ => None
  }
  object fibonacci {
    private def fib(n:BigInt,m:BigInt):Stream[BigInt] = n #:: fib(m,n+m)
    private val stream = fib(0,1)
    def apply(n:Int) = stream(n)
  }
}

object fibonacciも同様に

となっている。
こちらも大きな数値で呼ばれる程メモリを食いつぶす。

ServletContainer用定義

File:./src/main/webapp/WEB-INF/web.xml

<web-app>
  <servlet>
    <servlet-name>Factorial</servlet-name>
    <servlet-class>Factorial</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>Factorial</servlet-name>
    <url-pattern>/fac</url-pattern>
  </servlet-mapping>
  <servlet>
    <servlet-name>Fibonacci</servlet-name>
    <servlet-class>Fibonacci</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>Fibonacci</servlet-name>
    <url-pattern>/fib</url-pattern>
  </servlet-mapping>
</web-app>

XML自体の定義とか文字コードとか細かい事は今は気にしない。
HTMLの方でリクエスト先として指定している"/fac"と"/fib"を定義する。

結果

$ sbt "~;container:start; container:reload /"

こう呼び出すと、.scalaや.javaのソース変更を検知して自動的にコンパイル/リロードしてくれる。*1
http://localhost:8080/にアクセス。

フォームに数値を入力すると…?

階乗とフィボナッチ数列のN番目が表示された!
しかし大きな数値を入れると…。

Stream[BigInt]でNまでの階乗とN番目までのフィボナッチ数列を保持してるので、サーバのJVMがキッツキツになる。
限界を超えるとフリーズする。
こういう所でDBを使うべきなんでしょうな。
以上。

*1:Enterキーでsbtを終了できる

基礎からsbtとScalaでServletプログラミング No.1

WEB系の技術に関しての不足を感じている。
WEBのフレームワークを勉強したりしているが、いかんせん基礎知識が無いので難しい。

初めてプログラミングを学ぶ時にC言語から入るように、フレームワークとか便利な物を用いずに基礎をやってみようと考えた。
(私自身は初めてプログラミングを学ぶ際にはC言語が良いと思っているだけ。最近では最初はjavascriptもいいかなと思ったり。)

そこでJavaとWEBを繋ぐServletプログラミングをやってみようと思った。
jQueryを用いて動的にサーバとデータをやり取りして情報を提示していくようなプログラムをつくろうと思う。

sbtの準備

sbtのセットアップ方法は日本語ドキュメントがあるのでそちらを参照。
http://scalajp.github.com/sbt-getting-started-guide-ja/setup/
sbt-launch.jarと、それを呼ぶスクリプトをパスの通った場所に準備すればよい。

プロジェクト定義

まずは開発フォルダを作って、そこでsbtを実行してみる。

$ mkdir jetty-servlet
$ cd jetty-servlet/
$ sbt
[info] Set current project to default-6380f6 (in build file:/home/toming/Code/jetty-servlet/)
>

プロジェクトの定義が無いのでデフォルトのプロジェクトとして起動したようだ。
一度"exit"でsbtを終了し、定義を作成する。*1
File:./build.sbt

name := "jetty-servlet"

version := "0.0.1"

scalaVersion := "2.9.1"

seq(webSettings :_*)

libraryDependencies += "org.mortbay.jetty" % "jetty" % "6.1.22" % "container"

.sbtのファイルは各項目の間を空行で区切るのが必須のようですので、上記の空白は必要です。

File:./project/plugin.sbt

libraryDependencies <+= sbtVersion(v => v match {
  case "0.11.0" => "com.github.siasia" %% "xsbt-web-plugin" % "0.11.0-0.2.8"
  case "0.11.1" => "com.github.siasia" %% "xsbt-web-plugin" % "0.11.1-0.2.10"
  case "0.11.2" => "com.github.siasia" %% "xsbt-web-plugin" % "0.11.2-0.2.11"
})

その上で再度sbtを実行。

$ sbt
[info] Loading project definition from /home/toming/Code/jetty-servlet/project
[info] Updating {file:/home/toming/Code/jetty-servlet/project/}default-9318e8...
[info] Resolving com.github.siasia#xsbt-web-plugin_2.9.1;0.11.1-0.2.10 ...
[info] Resolving org.sonatype.oss#oss-parent;7 ...
[info] Resolving com.github.siasia#plugin-commons_2.9.1;0.11.1-0.1 ...
[info] Resolving org.scala-lang#scala-library;2.9.1 ...
[info] Resolving org.scala-tools.sbt#sbt_2.9.1;0.11.1 ...
[info] Resolving org.scala-tools.sbt#main_2.9.1;0.11.1 ...
[info] Resolving org.scala-tools.sbt#actions_2.9.1;0.11.1 ...
[info] Resolving org.scala-tools.sbt#classfile_2.9.1;0.11.1 ...
[info] Resolving org.scala-tools.sbt#io_2.9.1;0.11.1 ...
[info] Resolving org.scala-tools.sbt#control_2.9.1;0.11.1 ...
[info] Resolving org.scala-tools.sbt#interface;0.11.1 ...
[info] Resolving org.scala-tools.sbt#logging_2.9.1;0.11.1 ...
[info] Resolving org.scala-tools.sbt#process_2.9.1;0.11.1 ...
[info] Resolving org.scala-tools.sbt#classpath_2.9.1;0.11.1 ...
[info] Resolving org.scala-tools.sbt#launcher-interface_2.9.1;0.11.1 ...
[info] Resolving org.scala-lang#scala-compiler;2.9.1 ...
[info] Resolving org.scala-tools.sbt#incremental-compiler_2.9.1;0.11.1 ...
[info] Resolving org.scala-tools.sbt#collections_2.9.1;0.11.1 ...
[info] Resolving org.scala-tools.sbt#api_2.9.1;0.11.1 ...
[info] Resolving org.scala-tools.sbt#persist_2.9.1;0.11.1 ...
[info] Resolving org.scala-tools.sbinary#sbinary_2.9.0;0.4.0 ...
[info] Resolving org.scala-tools.sbt#compile_2.9.1;0.11.1 ...
[info] Resolving org.scala-tools.sbt#ivy_2.9.1;0.11.1 ...
[info] Resolving org.apache.ivy#ivy;2.2.0 ...
[info] Resolving com.jcraft#jsch;0.1.31 ...
[info] Resolving commons-httpclient#commons-httpclient;3.1 ...
[info] Resolving commons-logging#commons-logging;1.0.4 ...
[info] Resolving commons-codec#commons-codec;1.2 ...
[info] Resolving org.scala-tools.sbt#completion_2.9.1;0.11.1 ...
[info] Resolving jline#jline;0.9.94 ...
[info] Resolving org.scala-tools.sbt#run_2.9.1;0.11.1 ...
[info] Resolving org.scala-tools.sbt#task-system_2.9.1;0.11.1 ...
[info] Resolving org.scala-tools.sbt#tasks_2.9.1;0.11.1 ...
[info] Resolving org.scala-tools.sbt#tracking_2.9.1;0.11.1 ...
[info] Resolving org.scala-tools.sbt#cache_2.9.1;0.11.1 ...
[info] Resolving org.scala-tools.sbt#testing_2.9.1;0.11.1 ...
[info] Resolving org.scala-tools.testing#test-interface;0.5 ...
[info] Resolving org.scala-tools.sbt#compiler-interface;0.11.1 ...
[info] Resolving org.scala-tools.sbt#precompiled-2_8_1;0.11.1 ...
[info] Resolving org.scala-tools.sbt#precompiled-2_8_0;0.11.1 ...
[info] Resolving org.scala-tools.sbt#precompiled-2_9_0;0.11.1 ...
[info] downloading http://repo1.maven.org/maven2/com/github/siasia/xsbt-web-plugin_2.9.1/0.11.1-0.2.10/xsbt-web-plugin_2.9.1-0.11.1-0.2.10.jar ...
[info] 	[SUCCESSFUL ] com.github.siasia#xsbt-web-plugin_2.9.1;0.11.1-0.2.10!xsbt-web-plugin_2.9.1.jar (1846ms)
[info] Done updating.
[info] Set current project to jetty-servlet (in build file:/home/toming/Code/jetty-servlet/)
[info] Updating {file:/home/toming/Code/jetty-servlet/}default-6380f6...
[info] Resolving org.scala-lang#scala-library;2.9.1 ...
[info] Resolving org.mortbay.jetty#jetty;6.1.22 ...
[info] Resolving org.mortbay.jetty#project;6.1.22 ...
[info] Resolving org.mortbay.jetty#jetty-parent;8 ...
[info] Resolving org.eclipse.jetty#jetty-parent;9 ...
[info] Resolving org.mortbay.jetty#jetty-util;6.1.22 ...
[info] Resolving org.mortbay.jetty#project;6.1.22 ...
[info] Resolving org.mortbay.jetty#servlet-api;2.5-20081211 ...
[info] downloading http://repo1.maven.org/maven2/org/mortbay/jetty/jetty/6.1.22/jetty-6.1.22.jar ...
[info] 	[SUCCESSFUL ] org.mortbay.jetty#jetty;6.1.22!jetty.jar (4044ms)
[info] downloading http://repo1.maven.org/maven2/org/mortbay/jetty/jetty-util/6.1.22/jetty-util-6.1.22.jar ...
[info] 	[SUCCESSFUL ] org.mortbay.jetty#jetty-util;6.1.22!jetty-util.jar (1644ms)
[info] Done updating.
> 

実際は初回の実行だともっと結果は長くなる。
sbtはダウンロードしたjar等をキャッシュしている為、過去に落としたjarはそれを利用する。
これで依存関係の設定は完了。

web.xml定義

File: ./src/main/webapp/WEB-INF/web.xml

<web-app>
  <servlet>
    <servlet-name>Test</servlet-name>
    <servlet-class>toming.servlet.Test</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>Test</servlet-name>
    <url-pattern>/test</url-pattern>
  </servlet-mapping>
</web-app>

これで下記のように設定される。

  1. Testという名前のServletを登録
  2. toming.servlet.Testというクラスに紐付け
  3. "/test"というパターンのアドレスにアクセスがあった際に呼び出す

Servlet定義

そしていよいよServletそのもののクラスを作成。
File:./src/main/scala/Test.scala

import javax.servlet.http._

class Test extends HttpServlet {}

sbtで"compile"してみる。

> compile
[info] Compiling 1 Scala source to /home/toming/Code/jetty-servlet/target/scala-2.9.1/classes...
[error] /home/toming/Code/jetty-servlet/src/main/scala/Test.scala:1: object servlet is not a member of package javax
[error] import javax.servlet.http._
[error]              ^
[error] one error found
[error] {file:/home/toming/Code/jetty-servlet/}default-6380f6/compile:compile: Compilation failed
[error] Total time: 0 s, completed 2012/04/14 0:19:48
> 

javax.servletが無い…だと…。
こういう時は、"console-quick"が便利。*2

> console-quick
[info] Starting scala interpreter...
[info] 
Welcome to Scala version 2.9.1.final (OpenJDK 64-Bit Server VM, Java 1.6.0_23).
Type in expressions to have them evaluated.
Type :help for more information.

scala> javax.
accessibility   activation      activity        annotation      crypto          imageio         jws             lang            management      
naming          net             print           rmi             script          security        smartcardio     sound           sql             
swing           tools           transaction     xml             

クラスがそもそもクラスパスに無いようだ。
build.sbtにservlet-apiを追加。

name := "jetty-servlet"

version := "0.0.1"

scalaVersion := "2.9.1"

seq(webSettings :_*)

libraryDependencies += "org.mortbay.jetty" % "jetty" % "6.1.22" % "container"

libraryDependencies += "javax.servlet" % "servlet-api" % "2.5"

これでビルドOK

> compile      
[info] Compiling 1 Scala source to /home/toming/Code/jetty-servlet/target/scala-2.9.1/classes...
[success] Total time: 1 s, completed 2012/04/14 0:33:25
> 

"container:start"でjettyを開始してServletを使用可能にできる。

> container:start
2012-04-14 00:36:18.376:INFO::Logging to STDERR via org.mortbay.log.StdErrLog
[info] jetty-6.1.22
[info] NO JSP Support for /, did not find org.apache.jasper.servlet.JspServlet
[info] Started SelectChannelConnector@0.0.0.0:8080
[success] Total time: 0 s, completed 2012/04/14 0:36:18
> 

現状のServletでは何もできないので、doGetを定義してブラウザに応答を返せるようにする。

import javax.servlet.http.{HttpServlet, HttpServletRequest, HttpServletResponse}

class Test extends HttpServlet {
  override def doGet(req:HttpServletRequest, res:HttpServletResponse) {
    res.setContentType("text/html")
    res.setCharacterEncoding("UTF-8")
    res.getWriter().write((
      <html>
        <header />
        <body>
          Hello Servlet.
        </body>
      </html>
    ).toString)
  }
}

sbtでコンパイル後、"container:reload"すると動作中のjettyに反映される。

> container:reload /
[info] NO JSP Support for /, did not find org.apache.jasper.servlet.JspServlet
[success] Total time: 0 s, completed 2012/04/14 0:51:32
> 

動作確認

"http://localhost:8080/test"にアクセスしてみると…。

キターーー!
長々と書いた割に初歩の初歩しかやってませんが、自己の記録の為。

*1:参考:https://github.com/siasia/xsbt-web-plugin/wiki/

*2:"compile"が通る時は"console"でOK

ScalaのmapをJavaでやりたかった…。

Scalaのmap

Scalaでとにかく便利なのはコレクションに対するmapのメソッド。

scala> List(1,2,3,4,5).map { i => (i + 100).toString }
res0: List[java.lang.String] = List(101, 102, 103, 104, 105)

Listにmapで関数を渡すと、各要素を引数として関数を評価し、その結果をコレクションで返してくれます。上記の例では入力がInt、出力がStringの関数を渡して、結果の文字列をListで受け取っています。

Javaでやりたかった…

とりあえずJavaのコレクションを継承したクラスにmapを持たせようと思ったがこんなことになった。
Scalaみたいにできるだけ短く書きたい。」
「関数を定義する為だけにクラスを増やしたくない」

public interface C<I,O> {
	public O exec(I i);
}

まずは関数定義の為に短い名前のインタフェースを定義。
これを実装したインスタンスを使用したいその場で作成して投げる。
汎用的なものはちゃんとクラス定義しておく。

import java.util.ArrayList;

public class ArrayListW<T> extends ArrayList<T> {
	public <O> ArrayListW<O> map(C<T,O> function) {
		ArrayListW<O> result = new ArrayListW<O>();
		for(T t : this) result.add(function.exec(t));
		return result;
	}
}

そしてJavaArrayListを継承したクラスを作成し、mapメソッドを作成。

public class Test {
	public static void main(String[] args) {
		ArrayListW<Integer> foo;
		ArrayListW<String> bar;
		C<Integer,String> mapper = new C<Integer,String>(){
			public String exec(Integer i) {
				return new Integer(i+100).toString();
			}
		};
		foo = new ArrayListW<Integer>();
		for(int i=1; i<=5; i++) foo.add(i);
		bar = foo.map(mapper);
		System.out.println(bar);
	}
}

適当なお試しコード。やっぱり長ったらしくなった…。

$ java Test
[101, 102, 103, 104, 105]

まあ動きは想定通りだけど、やっぱり使いづらそう。
Java標準のクラスにもScalaのmap欲しいけど、関数を渡せないからなぁ…。
Callbackインタフェースを使用してDBを操作するようなAPISeasar2やSpringにはあるのに…。
パイプライン処理のような書き方はなかなかできないらしいな。

Scalaのtryは値を返せる!

ScalaJavaでちょっと自分で書いてていまいちだと思うのが下記のようなコード。

object Foo {
  def create(file:File):List[Foo] = {
    val in = new FileInputStream(in)
    val result = Source.fromInputStream(in).getLines.map(new Foo(_)).toList
    in.close
    result
  }
}
class Foo(src:String) {
  /* 色々 */
}

メソッド内で開いたファイルを閉じなきゃいけないが、それは返す値を取得した後に行う必要がある。
どうもここが格好つかない…。
と思っていたが、Scalaのif文が値を返す事に関連して、tryも値を返すのでは?と思ったら大当たり。
(ドキュメント読めよ…という心のツッコミはご勘弁いただきたい。)

scala> try { throw new Exception } catch { case _ => 1 }
res0: Int = 1

という事で、リソースのクローズを最後に行いつつ、結果をそのまま返却できる方法が下記の通り。

object Foo {
  def create(file:File):List[Foo] = {
    lazy val in = new FileInputStream(in)
    try {
      Source.fromInputStream(in).getLines.map(new Foo(_)).toList
    } finally {
      in.close
    }
  }
}
class Foo(src:String) {
  /* 色々 */
}

これでresultなんて無意味に値を束縛する事なく結果を返却できる。
(結果に「結果」なんて名前も付けずに済むよ!)

Java実装地獄の門

Java実装地獄の門」という言葉が頭をよぎったので記事にすることにする。
というのも、仕事で触れるJavaのコードがまさにそれなのだ。

この門をくぐる者は一切の希望を捨てよ

wikipedia:地獄の門より

地獄の門」でググって初めて元ネタを知ったけど、一切希望が持てない点は共通している。

なお愚痴なので「わかりやすく」とかあまり考えてないので悪しからず。
ちなみにコード例は妄想ではなく、実際に自分がぶち当たったコードをわかりやすく噛み砕いたものです。

実装

まずこんなクラスがあったとする

public class FooContext {
	String id;
	public void setId(String id) {
		this.id = id;
	}
	public String getId() {
		return id;
	}
}

Contextって名称が「いい名前が思いつきませんでした(ゝω・)vキャピ 」って感じですな。
ContextのContext(文脈)が不明になっている。

public class FooCtrlLogic {
	private FooContext context = null;
	public void setFooContext(FooContext context) {
		this.context = context;
	}
	public FooContext getFooContext() {
		return this.context;
	}
	public void doHogeHoge() {
		/* 何らかの色々複雑な処理 */
	}
	public void doFugaFuga() {
		/* 何らかの色々複雑な処理 */
	}
	public void execute() {
		String id = context.getId();
		if(id.equals("000000")) { //実はIDは数値やUUIDのStringだったりする
			doHogeHoge();
			return; //ここでreturn
		}
		doFugaFuga();
		return; //ここでもreturn
		//↑だったらif(){}else{}を使おうよ…。
	}
}

まずはFooCtrlLogic#executeの所。

public void execute() {
	String id = context.getId();
	if(id.equals("000000")) { //実はIDは数値やUUIDのStringだったりする
		doHogeHoge();
		return; //ここでreturn
	}
	doFugaFuga();
	return; //ここでもreturn
	//↑だったらif(){}else{}を使おうよ…。
}
context.getId()

context.getId()した時点でどんなcontextがセットされているかわからない。
加えてdoHogeHoge,doFugaFuga内でもcontextに変更を加えている可能性もありますね。
というかまず間違いなくスレッドセーフではない。
ここへ変更を加える場合、膨れ上がったソースの様々な箇所を確認する必要が生まれる。
確認が漏れた上でロジックに変更加えて不具合出て怒られたらと思うと…(´・ω・`)

id.equals("000000")

わざわざ文字列を出してきてequals比較。
どこかで知らない誰かが( id == "000000" )で比較しちゃう可能性を考えると、Context自体に比較用のメソッドを作るべきであろう。

複数return

仕事してて結構見かけるのがコレ。中華ソース。
1つのメソッド中に大量に現れるif文+return。
制御の流れを追うのが非常に面倒になる。

public void... public void...

返り値がvoidのメソッドが大量にあり、複雑にネストした形で呼び出される形のロジックが組まれていると最悪。
ソースを追いかけるのにだいぶ時間がかかる…。

という愚痴。
ちゃんとこういうダメさを人に説明できるようにならなきゃなぁ。