XML-RPC してみる
XML-RPC は簡単に言ってしまえば...
インターネット上のサービスを簡単に実行するための仕組み。
もちろん サーバとのやり取りには XML を使う。
当然、サービスを提供する側が XML-RPC に対応していないとダメ。
XML-RPC の良いところは、
- REST と違い ちゃんと仕様が決まっていること。
- SOAP と違い 仕様が複雑でないこと。
これは、ライブラリを作る上では非常に重要な要素で、実際 XML-RPC は多くの言語で実装されている。
もちろん、JavaFX で使えるライブラリも存在する。
と言っても、Java のライブラリなのだが...
既存の Java ライブラリがそのまま利用できるのも JavaFX の良いところかな。
今回は、Apache XML-RPC 3.1.2 を使ってみることに...
プロジェクトページは こちら
まずは Apache XML-RPC をダウンロードして、解凍したファイルの中から
- ws-commons-util-1.0.2.jar
- xmlrpc-common-3.1.2.jar
- xmlrpc-client-3.1.2.jar
基本的な使い方はこんな感じ...
import java.net.URL; import org.apache.xmlrpc.client.XmlRpcClient; import org.apache.xmlrpc.client.XmlRpcClientConfigImpl; // URL 設定 def config = new XmlRpcClientConfigImpl(); config.setServerURL(new URL("http://b.hatena.ne.jp/xmlrpc")); // XmlRpcClient 生成 def client = new XmlRpcClient(); client.setConfig(config); // はてなブックマーク件数取得API 実行 // 第一引数: メソッド名 // 第二引数: パラメータ (配列) println(client.execute("bookmark.getTotalCount", [ "http://d.hatena.ne.jp/bluepapa32/" ]));リフレクションを使ってメソッドを invoke するのとほぼ同じで とっても簡単!!
今回は 最も簡単と思われる XML-RPC のサービス
はてなブックマーク件数取得APIを実行して、(とっても少ない...) 本ブログのはてなブックマークの数を標準出力してみた。
XML-RPC のことなんて何も知らなくても
たった 5行のコードを書くだけで 誰でも WEB サービスを実行できてしまうのだ。
このように とっても簡単な XML-RPC ではあるが、JavaFX で利用する上で気を付けなくてはならない点もある。
それは また次回...
WEB サービスと連携してみる
最終更新から早いもので4ヶ月も経ってしまった。
既に JavaFX も 1.3.1 に...
そろそろ JavaFX を再開してみることに。
仕事が忙しいことを言い訳に ここ暫く見て見ぬフリをしてきたが、
久しぶりに触ってみると、やっぱり面白いね。JavaFX...
なんでこうも流行らないんだろうか。特に日本では...
と言うことで...
何か面白いものを作りたいところだが、
もちろん、直ぐには思い浮かぶはずもなく...
とりあえず JavaFX は WEBサービスと連携しないとつまらない!!
という勝手な思い込みのもと WEBサービスとの連携についてまとめてみることに...
インターネット上のサービスと連携する場合、当然
あたりの HTTP をベースとした仕組みを使うのが常識?
と言っても...
インターネット上の WEBサービスの ほとんどは
REST で公開されているのが現状だろう。
インターネット上で SOAP を見かけることはほとんどないし...
XML-RPC で公開されているサービスは現在でも存在はしているものの あまり多くない。
個人的には割と好きなんだが...
そう考えると...
クライアント側では REST と 一応 XML-RPC がサポートできれば、
世の中のほぼ全てのサービスと連携できるはずである。
と言うことで...
次回から JavaFX で REST と XML-RPC を利用する方法についてまとめてみることに...
今回はブログの世界に社会復帰するためのネタふり...
早速次回は 大人の事情により メインの REST ではなく XML-RPC について。
シーケンスは参照渡しできない
前回に続き 今回もシーケンスについてです。
突然ですが、以下のコードを実行すると標準出力には何が出力されるでしょう?
var s1 = [ 1 ]; var s2 = s1; println(isSameObject(s1, s2)); // 同一インスタンスか確認答えは true です。
当たり前すぎて問題になりませんね。 ^ ^;)
では これは?
var s1 = [ 1 ]; var s2 = s1; insert 2 into s2; println(isSameObject(s1, s2)); // 同一インスタンスか確認実はこれは false になります。
上記のコードとの違いは 変数 s2 のシーケンスに 2 を追加しているだけですので、 常識で考えれば 結果は true になりそうなのに...
実際にシーケンスを標準出力してみると
s1: [ 1 ] s2: [ 1, 2 ]のように別ものになっています。
実はここに驚愕の事実が...
シーケンスはオブジェクトの参照を渡すことができない のです。
つまり、変数には シーケンスのコピーが渡されるということです。
実際は上記のように 要素が変更されるまでは コピーではなく 同一オブジェクトを参照しているのですが、 これは 要素が全く同じであれば 同じオブジェクトでもコピーしたオブジェクトでも何ら違いはないので 少しでもリソースを節約するために 本当に必要になるまで コピーするタイミングを遅らせているだけではないでしょうか。
今回のクセも気をつけないとハマりやすいです。
例えば、こんなコードは割と普通に書きそうになりますが 期待通りの動きにはなりませんので注意しましょう。
var group = Group {} var content = group.content; insert Rectangle {} into content;
シーケンスのデフォルト値
JavaFX の特徴の一つでもある シーケンス...
かなりクセがありますが、慣れてしまえば意外に便利。
と言うことで、少しシーケンスの基本をまとめておこうと思ったのですが...
結局クセの部分から書くことに...
シーケンスのクセは割と多く 一度に書くのは大変なので、取り合えず 今回はシーケンスのデフォルト値について...
シーケンス型の変数のデフォルト値が 要素を持たないシーケンス であることは 周知の事実だと思います。
例えば...
var content: Node[];と変数を宣言した場合、content の値は要素を持たないシーケンスになります。
また、次のように変数宣言時に null で初期化しても全く同じ結果になります。
var content: Node[] = null;null は 要素を持たないシーケンスに変換されてしまいます。シーケンスには null はないのです。
かなり違和感がありますが、こんな比較式でも true になります。
[] == null
ここまでは 想定内のお話ですが...
さらに驚くことに 実は 要素を持たないシーケンスは 型が同じであれば 全く同じインスタンス なのです。
例えば...
var s1 = [ "1" ]; var s2 = [ "1" ]; println(isSameObject(s1, s2));の結果は もちろん false になります。この結果は世間一般の常識です。
しかし...
var s1: String[] = []; var s2: String[] = []; println(isSameObject(s1, s2));の結果は違うのです。なんと true になります。
要素を持たないシーケンスのインスタンスは型ごとに一つずつしか生成されないようです。 デフォルト値が 要素を持たないシーケンスなので わからなくもないのですが...
さらに驚くことに 要素を持たないシーケンスに値を追加すると シーケンスのインスタンスがこっそりとすり替えられてしまう のです。
同じ型のデフォルト値が全て同じインスタンスである時点で当然の結果ですが...
例えば、常識的には 以下のコードのように シーケンスに 要素を追加しても シーケンスのインスタンス自体を変更している訳ではないので 当然 true になるはずなのですが そうではないのです。
以下の結果は なんと false ...
var s: String[]; var before = java.lang.System.identityHashCode(s1); insert "Hello" into s; var after = java.lang.System.identityHashCode(s1); println(before == after);
つまり、シーケンスのインスタンス自体 別物になってしまっているのです。
もはや、私の常識は なんの役にも立たない...
これが本当に仕様であるなら、かなり注意しないと思わぬバグを生みそうだ。
HttpRequest が完了しない
また HttpRequest ではまった...
今度はなんと...
HttpRequest がいつまで経っても完了しない...
まず HttpRequest#start() を実行して システムに認証後、HttpHeader から set-cookie を読み取り、それを HttpHeader にセットして 再度 HttpRequest#start() で別のページにアクセスしたかった。
ただそれだけなのに...
どうしても 一度目の HttpRequest の onDone が実行されない。
サーバはちゃんとレスポンスを返しているし...
ステータスコードも 200、ヘッダもちゃんと返って来ているのに...
コードは こんな感じ...
var request: HttpRequest; request = HttpRequest { location: "http://localhost:8080/foo/bar/hoge" headers: HttpHeader.basicAuth("guest", "guest"); // BASIC 認証 // ステータスコード 出力 onResponseCode: function(code) { println(code); } // レスポンス ヘッダ 出力 onResponseHeaders: function(names) { for (name in names) { println("{name}: {request.getResponseHeaderValue(name)}"); } } onDone: function() { // レスポンスが返ってきたら 再度 HttpRequest#start() } } request.start();
まさか!! スレッドがロックしてる?
と言うことで スレッドダンプを取ってみた...
java.lang.Thread.State: WAITING (on object monitor) at java.lang.Object.wait(Native Method) - waiting on <0x0000000106b449d0> (a com.sun.javafx.io.http.impl.WaitingInputStream) at java.lang.Object.wait(Object.java:485) at com.sun.javafx.io.http.impl.WaitingInputStream.waitUntilClosed(WaitingInputStream.java:88) - locked <0x0000000106b449d0> (a com.sun.javafx.io.http.impl.WaitingInputStream) at com.sun.javafx.io.http.impl.HttpTask.tryRun(HttpTask.java:227) at com.sun.javafx.io.http.impl.HttpTask.run(HttpTask.java:31) at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:441) at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:303) at java.util.concurrent.FutureTask.run(FutureTask.java:138) at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908) at java.lang.Thread.run(Thread.java:637)
スレッドダンプを見てみると...
5行目に
WaitingInputStream.waitUntilClosed ?
しかも6行目には locked !!
えっ、InputStream を close するまで wait する?
と言うことで...
その通りに onInput を追加して InputStream を close してみることに...
var request: HttpRequest; request = HttpRequest { location: "http://localhost:8080/foo/bar/hoge" headers: HttpHeader.basicAuth("guest", "guest"); // BASIC 認証 // ステータスコード 出力 onResponseCode: function(code) { println(code); } // レスポンス ヘッダ 出力 onResponseHeaders: function(names) { for (name in names) { println("{name}: {request.getResponseHeaderValue(name)}"); } } // InputStream を close onInput: funrction(in) { in.close(); } onDone: function() { // レスポンスが返ってきたら 再度 HttpRequest#start() } } request.start();
おっ、今度は ちゃんと onDone が実行された!!
どうやら HttpRequest では InputStream の close が必須ということらしい...
API ドキュメントにも微妙ではあるが 必ず close しなさいと書いてあった。
しかし、ヘッダの情報だけが必要な場合でも IntputStream を close しないといけないというのはどうだろうか...
Undo / Redo on JavaFX
今回は、JavaFX で Undo / Redo してみようと思います。
Undo / Redo は ちょっとまともなアプリケーションを作ろうとすると どうしても避けられない
機能です。
JavaFX で Undo / Redo する方法としては
- swing の UndoManager を使う
- 自分でがんばる
自分で Undo / Redo を実装する場合、まずはどの様に現在の情報を保存しておくかを決めないと...
保存する情報は次の2つ...
- 状態
- 手続 (手順)
これらは DB に例えるなら...
前者は スナップショットをテーブルやファイルに保存しておくようなイメージ。
状態そのものをデータとして保存しておきます。
後者は Redo ログ の様な感じ。
データそのものではなく、元に戻すための手順を保存しておきます。
どちらを使うかは状況次第?
もちろん、組み合わせて使うのもありですが...
今回は後者で実装することに...
なぜ、後者なのかというと JavaFX には
- 関数型の変数
- クロージャ
これらを利用すれば、簡単に元に戻すための手順を保存しておくことができます。
まずは 今回紹介する Undo / Redo を実際に試してみましょう。
では、早速 実装ですが...
今回は undo, redo が可能な Drag and Drop を実装してみましょう。
まずは
- undo, redo の手順を保持するためのクラス Undoable
- Undoable を管理するためのクラス UndoManager
本来であれば Command パターンの方がよいのかも...
でも 今回は undo, redo がメインと言うことで...
次は、以下のように undo, redo の手順を示す関数を保存します。
var umgr = UndoManager { limit: 100 } Draggable { node: node var translateX: Number; var translateY: Number; // Drag 前の位置を保存 onStart: function(e) { translateX = node.translateX; translateY = node.translateY; } // undo / redo の手順を保存 onDone: function(e) { undoables.add(Undoable { // undo の手順を示す関数を生成するブロック undo: { def _translateX = translateX; def _translateY = translateY; function(): Void { node.translateX = _translateX; node.translateY = _translateY; } } // redo の手順を示す関数を生成するブロック redo: { def _translateX = node.translateX; def _translateY = node.translateY; function(): Void { node.translateX = _translateX; node.translateY = _translateY; } } }); } }
Draggable は 『Drag and Drop on JavaFX Part 1』 で紹介したものに onStart と onDone を追加しています。
onStart は Drag が開始される前に実行されるコールバック関数で Drag する前の位置を保存しています。
onDone は Drag が終了した後に実行されるコールバック関数で undo / redo の手順を保存しています。
undo の手順を保存するコードは
undo: { def _translateX = translateX; def _translateY = translateY; function(): Void { node.translateX = _translateX; node.translateY = _translateY; } }たったこれだけですが
- ブロック
- 関数型の変数
- クロージャ
ちなみに、ブロックとは "{" と "}" で囲んだ式の固まりで 全てブロックのスコープ内で評価され、最後の式の結果のみが戻り値として返されます。
これは 以下のように無名関数を評価するのとほぼ等価です。
undo: (function() { def _translateX = translateX; def _translateY = translateY; function(): Void { node.translateX = _translateX; node.translateY = _translateY; } })();今回の場合、関数オブジェクトがブロックから返され、関数型の変数 undo にセットされます。
ここで大事なポイントですが、関数内で利用したい値は ブロック内の変数に格納しておくこと。
と言うのも、関数が評価されるタイミングは 関数の宣言時ではなく UndoManager#undo() がコールされたときだからです。 ちなみにこういうヤツを巷ではクロージャと呼んでいます。
もし、以下のように関数の中で 外部からアクセス可能な変数を直接参照してしまったら、関数が評価されるころには その変数の内容は すでに期待しているものではないでしょう。
undo: function(): Void { node.translateX = translateX; node.translateY = translateY; }
後は 実際に undo, redo するためのボタンを用意するだけです。
以下のようにとっても簡単です。
undoable, redoable はそれぞれ undo, redo できるかどうかを表す変数で、これを Button#disable に bind しておけば
実際に undo, redo できる時だけボタンを有効にできるのです。なんて楽なんだろう。
undo(), redo() 関数は UndoManager に保存してある 手順を元に undo, redo します。
Button { text: "Undo" disable: bind not umgr.undoable action: umgr.undo } Button { text: "Redo" disable: bind not umgr.redoable action: umgr.redo }
どうでしょうか?
JavaFX なら とっても簡単に Undo / Redo が実現できることがわかっていただけたのではないでしょうか?
変数名、関数名に予約語を使ってみる
今年に入って JavaFX で少しまともなアプリケーションを作成するためのライブラリを準備しているのですが、 (なかなか進みませんが、そのうち紹介できるかな...)
ちょっとした発見があったので、メモを兼ねて 約一ヶ月ぶりのエントリ...
それ程こだわってきた訳でもないですが、そろそろ英語のタイトルもつらくなってきたので日本語にすることに...
で早速 ちょっとした発見ですが...
なんと JavaFX では変数名や関数名に予約語が使えるのです。
目からウロコ、Java の常識ではちょっと考えられない...
実はちょっとした間違えで予約語を変数名に使ったところ、コンパイル時に
Perhaps you tried to use a keyword as the name of a variable (use <<keyword>> if you need to do this).のようなエラーメッセージが...
まさか... と思いながらも 試しに以下のように予約語を << >> で括ってみたところ、
なんとちゃんとコンパイルが通るではないですか!!
Main.fx
function <<delete>>(): Void { println("delete"); } public class Main { public var <<reverse>>: Boolean; function <<init>>(): Void { println("init"); } }
もちろん、ちゃんと実行もできます。
ただし、予約語を使った変数や関数を利用する場合は少しだけ注意が必要です。
例えば、上記の Main.fx の run 関数内で
public function run() { delete(); }のようにスクリプト関数をそのまま使うとコンパイルエラーになるので
public function run() { Main.delete(); }のようにクラス名を付けないとダメみたい...
また、Main クラス内で関数を利用する場合、例えばクラスの初期化時に
init { init(); }とするとコンパイルエラーになるので
init { this.init(); }のように this を付けましょう。
このようにちょっと注意すれば、結構使える仕様なのですが...
一つだけ問題が...
いろいろ試してみたのですが、たぶん インスタンス生成時に変数を初期化する場合は
var main = Main { <<reverse>>: true }; main.reverse = false;のように << >> で括るしかない...
もちろん、初期化後に値を代入する場合は << >> で括る必要はないのですが...
クラスを利用する側にも << >> を強要するのはちょっといただけない。 と言うことで 変数に予約語を使うのは避けた方が良いと思います。
もちろん、関数についても 予約語を使わないに越したことはないのですが...
init, insert, delete 等は普通に使いたかったりします。
関数について言えば、上記の注意点は いずれも関数を定義したクラス内での話なので、
作る側さえ注意すれば、そのクラスを利用する側は特に意識する必要はないはず...
クラスを利用する側は 関数名のみを指定することはなく 必ず
var main = Main {} main.insert(hoge);のように記述するので、予約語の関数であってもコンパイルエラーになることはないからです。
と言うことで、関数名に予約語はありではないでしょうか...
ただ、static import の場合は、関数名のみで呼び出せてしまうので、スクリプト関数で予約語を使うのは避けた方がよいかも...