2012年12月6日木曜日

WicketでWebSocketを使ってみた

Wicketの6からWebSocketに対応したらしいので使ってみた。

  • 作ったもの
  • 感想
  • 引っかかったところ
作ったもの
簡単にいうと、チャットアプリケーション。
メッセージをAjaxで受け取り、チャット参加者へWebSocketでpush通知する。
クライアント側はjQueryで受け取ったメッセージをDOMへ追加する。

引っかかったところ
IWebSocketConnectionにはComponentをaddできない。送信できるのは文字列またはbyte配列。WebSocketRequestHandlerにはaddできるので、WebSocketBehaviorのonMessageで実装すればよいが、それってAjaxとなにが違うのかな?

web.xmlで、サーブレットコンテナに対応したfilterを設定しなければならない。wikiのCustom WicketFilterを参照。

JavaScriptの依存関係を設定しなければいけない。サンプルを参照。

感想
IWebSocketConnectionで送受信できるデータが文字列かバイト配列なので、実際はJSONを送受信することになると思われる。Javaオブジェクトとの相互変換を考慮する必要があると思う。

push通知が動作するまでは、手間がかかる割に動作はAjaxと変わらないのでモチベーションが上がらなかった。が、push通知で画面が更新されるようになると一気に楽しくなった。

やや話はそれるが、JavaScriptのライブラリがjQueryになったのも大きいと思う。

2012年9月23日日曜日

ClojureでDesign By Contract

Clojureでは、関数を定義する際に事前条件と事後条件を定義できます。
この機能を使えば、Design By Contract(契約による設計)を容易に実践できます。

事前条件を定義する
さっそく実験してみます。
「パラメータが文字列であること」という事前条件をもつ関数aを定義しました。

user=> (defn a [x] {:pre [(instance? String x)]} x)
#'user/a
user=> (a 1)
AssertionError Assert failed: (instance? String x) user/a (NO_SOURCE_FILE:1)
user=> (a "123")
"123"
view raw assert.clj hosted with ❤ by GitHub
パラメータとして1を渡したときにAssertionErrorが発生しました。

Assertionを無効にする
Assertionを無効にするには、*assert*へfalseをセットします。
この値はコンパイル時に参照されるため、REPLの場合はdefnを実行する前に設定しておく必要があります。
*assert*へfalseを設定してから、上と同じ関数を定義しました。

user=> (set! *assert* false)
false
user=> (defn a [x] {:pre [(instance? String x)]} x)
#'user/a
user=> (a 1)
1
user=> (a "123")
"123"
パラメータとして1を渡しても例外が発生しなくなりました。

例をもう少し
最後に、もう少し実践的な例として日付の比較をする関数を上げておきます。
この関数はCalendar.beforeを呼ぶため、パラメータはCalendarクラスのインスタンスでなければいけません。

user=> (import java.util.Calendar)
java.util.Calendar
;; 関数定義
user=> (defn before? [base comp]
#_=> {:pre [(instance? Calendar base)
#_=> (instance? Calendar comp)]
#_=> :post [(instance? Boolean %)]}
#_=> (.before base comp))
#'user/before?
;; 動作確認
user=> (def cal1 (Calendar/getInstance))
#'user/cal1
user=> (def cal2 (Calendar/getInstance))
#'user/cal2
user=> (before? cal1 cal2)
true
user=> (before? cal2 cal1)
false
;; Calendar以外を渡してみる
user=> (before? 1 2)
AssertionError Assert failed: (instance? Calendar base) user/before? (NO_SOURCE_FILE:1)
view raw before.clj hosted with ❤ by GitHub
事前条件と事後条件をシンプルに定義できるのが素敵だと思いました。

2012年7月30日月曜日

DropDownChoiceでenumを使用する

突然ですが、enumって便利ですよね。
業務で使用する場面としては、"○○区分"というものを表現するのによく使います。
そしてそれを画面では<select>として表示することもよくあります。
このとき、画面に表示するテキストをenum自身から分離するための方法です。

enumを定義する

普通にenumを定義します。
public enum RockPaperScissors {
ROCK,PAPER,SCISSORS;
}

アプリケーションのプロパティファイルに表示する文字列を定義する

型名.フィールド名=文字列の形式で定義します。
# for RockPaperScissors
RockPaperScissors.ROCK=\u30b0\u30fc
RockPaperScissors.PAPER=\u30d1\u30fc
RockPaperScissors.SCISSORS=\u30c1\u30e7\u30ad

DropDownChoiceのコンストラクタにEnumChoiceRendererを渡す

public class RockPaperScissorsDropDownChoice
extends DropDownChoice<RockPaperScissors> {
public RockPaperScissorsDropDownChoice(String id) {
super(id,
Arrays.asList(RockPaperScissors.values()),
new EnumChoiceRenderer<RockPaperScissors>());
}
}

画面ごとに表示するテキストを変えたい場合、以下のようにします。

  1. 画面ごとにプロパティファイルを作成する
  2. EnumChoiceRendererのコンストラクタにPageのインスタンスを渡す



2012年6月26日火曜日

AngularJSとCompojureによる簡単なサンプル

Googleが開発しているAngularJS(以下angular)のバージョンが1.0になったらしいので、サンプルを作ってみました。最近はClojureに対する愛が高まっているので、サーバーサイドにはCompojureを選びました。angular-Compojure間をAjaxで通信することを目標に、一行掲示板のようなものを作りました。clj-anglr

苦労したこと
angularから送ったJSONをCompojure側で受け取るところ。

;; Good
(defroutes
(POST request (:param (read-json (slurp (:body request)))))
...)
;; Bad
(defroutes
(POST [param] param)
...)
view raw sample.clj hosted with ❤ by GitHub
Compojureのサンプルを検索してすぐに見つかるのはBadのほうだと思いますが、この書き方ではangularから送ったJSONを受信できません。JSONはリクエストパラメータとしてではなく、リクエストのBODYに格納されるため、Goodのように自分でBodyから取り出す必要がありました。

感じたこと
angularのマッピングは快適です。特に$scopeへ追加したオブジェクトを更新すると、何もしなくても画面に反映されるのがすごいと思いました。業務で作っているシステムは頻繁に画面がちらつく・・・。
もう一つ、サーバーサイドにCompojureというのはアリだと思いました。Clojureのデータ構造はシンプルなのでJavaに比べてなんとなく不安でしたが、JavaScriptと通信する時にJSONに変換するのであれば、大して問題にならない気がしました。むしろ、無駄な情報がないために見通しがいいとも感じました。

業務で使うことはまだないと思いますが、なかなか興味深いライブラリでした。

2012年3月23日金曜日

WicketTesterで独自のSessionを使用するには

アプリケーションで独自のSessionを使用するには、WebApplication.newSessionをオーバーライドします。
WicketTesterで単体テストをするときも、そのApplicationをコンストラクタに渡せば、そのSessionを使ってテストができます。
しかしこれは単体テストとしては今ひとつ。できればApplicationに依存しないテストを書きたい。Applicationのテストをしたいのではなく、Componentのテストをしたいのだ–。

MockApplicationを使う
何か適当な解決策はないものか、と考えたところで思いついたのが、MockApplicationを継承して使うという方法でした。
MockApplicationはWicketTesterのデフォルトコンストラクタが呼ばれたときに使用されるApplicationのため、もっともシンプルな解決策ではないかと考えました。
というわけで、MockApplication.newSessionをオーバーライドすれば独自のSessionが使えることを確認する試験を書きました。WicketTesterを起動した後、Sessionを取得して型を検査しています。

import org.apache.wicket.Session;
import org.apache.wicket.mock.MockApplication;
import org.apache.wicket.protocol.http.WebApplication;
import org.apache.wicket.protocol.http.WebSession;
import org.apache.wicket.request.Request;
import org.apache.wicket.request.Response;
import org.apache.wicket.util.tester.WicketTester;
import org.junit.Test;
import static org.junit.Assert.*;
public class SetSessionTest {
@Test
public void testSessionCreated() {
WebApplication app = new MockApplication() {
public Session newSession(Request request, Response response) {
return new CustomizedSession(request);
}
};
WicketTester tester = new WicketTester(app);
Session session = Session.get();
assertTrue(session instanceof CustomizedSession);
}
static class CustomizedSession extends WebSession {
CustomizedSession(Request request) {
super(request);
}
}
}
これでまたTDDの道を極めたぜ。

2012年3月18日日曜日

Javaのtrimについて

Javaで末尾の全角/半角スペースをトリムする
気になったことがあったので僕も実験してみた。
気になったのは、次の2つ。
  1. 正規表現のコンパイルを一回にしたらどれくらい改善できるのか
  2. 末尾から空白を検索した方が早くね?
というわけでメソッドを2つ追加して実験してみた。
結果は、
  1. trim2と同じか少し遅いくらいまで改善
  2. trim3と同じか少し早いくらい
  3. trim1はダントツで遅い
といった感じになりました。String.replaceAllは遅いですけど書きやすいので、問題を把握した上で使いましょう。
import java.util.regex.Pattern;
public class Main {
private static final String VALUE = "あいう    ";
public static void main(String[] args) {
long[] times = new long[6];
times[0] = System.nanoTime();
for(int i = 0; i < 10000; i++) {
trim1(VALUE);
}
times[1] = System.nanoTime();
for(int i = 0; i < 10000; i++) {
trim2(VALUE);
}
times[2] = System.nanoTime();
for(int i = 0; i < 10000; i++) {
trim3(VALUE);
}
times[3] = System.nanoTime();
for(int i = 0; i < 10000; i++) {
trim4(VALUE);
}
times[4] = System.nanoTime();
for(int i = 0; i < 10000; i++) {
trim5(VALUE);
}
times[5] = System.nanoTime();
String fmt = "trim%d: %d";
for(int i = 0; i < 5; i++){
String str = String.format(fmt, i + 1, times[i + 1] - times[i]);
System.out.println(str);
}
}
public static String trim1(String value) {
return value.replaceAll("( | )+\\z", "");
}
public static String trim2(String value) {
StringBuilder sb = new StringBuilder(value);
for(int i = sb.length()-1; 0 <= i; i--) {
char c = sb.charAt(i);
if(c == ' ' || c == ' ') sb.deleteCharAt(i);
else break;
}
return sb.toString();
}
public static String trim3(String value) {
char[] ary = value.toCharArray();
char[] trimed = new char[ary.length];
for(int i = ary.length-1; 0 <= i; i--) {
if(ary[i] == ' ' || ary[i] == ' ') continue;
else trimed[i] = ary[i];
}
return new String(trimed);
}
private static Pattern pattern = Pattern.compile("( | )+\\z");
public static String trim4(String value) {
return pattern.matcher(value).replaceAll("");
}
public static String trim5(String value) {
for(int index = value.length() - 1; 0 <= index; index--){
char chr = value.charAt(index);
if(chr == ' ' || chr == ' '){
continue;
}
return value.substring(0, index + 1);
}
return value;
}
}
view raw Main.java hosted with ❤ by GitHub

2012年3月8日木曜日

Code Year 2012 - Week 8

今週は2回目のブラックジャックゲーム。
行数が多いので見にくい。JavaScriptにはJavaのimportにあたるような機能はないのかな?

2012年3月3日土曜日

Code Year 2012 - Week 7

今週の課題はループ。
forとwhileだけじゃなく、再帰を使ったループもあった。
再帰は普段使わないからけっこう手間取った。
8週目の課題がこなかったのはもたもたしてたからかな?

2012年2月29日水曜日

Code Year 2012 - Week 6

今週はprototype。
prototypeプロパティに代入することで、動的にクラス階層を変えられるのがすごい。

2012年2月23日木曜日

Codecademyの課題をClojureで解いてみよう - その3

今回はDice Game
rand-intを使えば、最初から整数として返してくれるので楽チン。
サイコロの出目と点数を返す関数とそれを表示する関数にわけてもいいかもしれない。


(defn dice-game []
(let [die1 (+ 1 (rand-int 6))
die2 (+ 1 (rand-int 6))]
(printf "you rolled a %d and a %d for a score of %d\n"
die1
die2
(cond (or (= 1 die1) (= 1 die2)) 0
(= die1 die2) (* 2 (+ die1 die2))
:else (+ die1 die2)))))
view raw dice-game.clj hosted with ❤ by GitHub

2012年2月17日金曜日

Code Year 2012 - Week 5

Week 4を飛ばしてWeek 5。Week 4はJavaScriptの基本的な部分だったので正直あまり面白くなかった。
Week 5は面白かった。Constructorメソッドやら、オブジェクトリテラルやら。
Week 6も結構楽しみ。

2012年2月12日日曜日

Codecademyの課題をClojureで解いてみよう - その2

今回はタクシーの運賃を計算する関数を実装します。
letを使って数値に名前をつけました。
そしてletの中で通常料金の計算は終わっているという。

(defn taxi-fare [milesTraveled, pickupTime]
(let [
baseFare 2.50
costPerMile 2.00
nightSurcharge 0.50
cost (+ baseFare (* costPerMile milesTraveled))]
(if (or (<= pickupTime 6)
(<= 20 pickupTime))
(+ nightSurcharge cost)
cost)))
view raw taxy-fare.clj hosted with ❤ by GitHub

2012年2月7日火曜日

Codecademyの課題をClojureで解いてみよう - その1

Clojureの練習にCodecademyの課題を使ってみようと思いました。

第一問はIntroductionよりFizzBuzz。
あまりきれいではないな。

(for [x (range 1 101)]
(if (and (= (mod x 3) 0) (= (mod x 5) 0)) "FizzBuzz"
(if (= (mod x 3) 0) "Fizz"
(if (= (mod x 5) 0) "Buzz" x))))
view raw FizzBuzz.clj hosted with ❤ by GitHub

2012年2月6日月曜日

MyBatisの検索結果としてインターフェイスのリストを返す

Guiceをシステムに採用して半年がたち、すっかりインターフェイスが体に馴染みました。
こうなってくると永続化層のMyBatisにもインターフェイスを返して欲しくなります。
しかし、MyBatisは初期状態ではデフォルトコンストラクタがないクラスには対応していません。

ObjectFactoryの実装とバインド
この問題を解決するには、ObjectFactoryを実装し、MyBatisModule#bindObjectFactoryTypeメソッドでバインドします。
ObjectFactoryへProviderを注入
せっかくGuiceを採用しているのですから、ObjectFactoryへProviderを注入し、オブジェクトの生成を委譲しましょう。
実装したものが次のクラスです。

package myamamoto.sample.mapper;
import myamamoto.sample.bean.Person;
import org.apache.ibatis.reflection.factory.DefaultObjectFactory;
import com.google.inject.Inject;
import com.google.inject.Provider;
class BeanObjectFactory extends DefaultObjectFactory {
private static final long serialVersionUID = 1L;
private Provider<Person> personProvider;
@Inject
BeanObjectFactory(Provider<Person> personProvider) {
this.personProvider = personProvider;
}
@Override
public Object create(@SuppressWarnings("rawtypes") Class type) {
if(Person.class == type){
return this.personProvider.get();
}
return super.create(type);
}
}


2012年2月2日木曜日

Code Year 2012 - Week 3

3週目。復習が多くていまいち物足りない。
switchで文字列が使えるのはJavaと違って便利よいね。
3項演算子も使い慣れると便利。これはJavaにもある。

2012年1月22日日曜日

Code Year 2012 - Week 2

Code Year 2012の2週目。関数のお勉強。
2週目は3つコースが用意されている。とりあえず一つ目をこなした。

2012年1月19日木曜日

Code Year 2012 - Week 1

Code Year 2012の1週目。
JavaScriptの入門。変数の定義から、分岐や繰り返しを学んだ。
回答はgistに保存している。自分の勉強の記録のつもり。
まだまだ簡単。

2012年1月5日木曜日

メソッド名のリストを取得する

大したものではございませんが、REPLでJavaを呼び出すときに頻繁に入力しました。
(defn get-method-names [cls]
(map #(.getName %) (.getMethods cls)))

これをJavaでやろうとすると、キャストやら例外処理でぐちゃぐちゃになるんですよねえ。