mike、mikeなるままに…

プログラムに関してぬるま湯のような記事を書きます

JJUG LT大会で話した内容の要点

こんにちわ、みけです。

昨日(2013/08/19)に開催されたJJUGのLT大会でLTをしてきました。

LTの内容ですが、

ネタLTとしては大成功(?)でしたが、

まあJavaのLTとしては疑問符が残るものを発表しました。

とりあえず、JavaFXの部分だけ抽出して書きたいと思います。

要望・仕様

ウケ狙いをしたLTの「冒険者の広場』のページについて、次のような要望・仕様になっています。

  • 定期的にWebの情報を取得したい
  • Web APIが公開されていない
  • リンク先のURLがJavascriptでのクリックにバインドされた関数に記載されている

スクレイピングツールの候補

Javaで利用できるスクレイピングツールおよびブラウザーは次のものが主に挙げられます

  • NekoHTML/Jsoup
  • HtmlUnit
  • Selenium
  • JavaFXのWebEngine

NekoHtml

Java1.3以降から使えるHTMLパーサーです。

古くてかつ最近は更新が遅いようです。

最新のHTMLをパースするのには向いていません。

パースするだけなのでJavascriptの実行はできません。

したがって、採用しませんでした。

Jsoup

HTML5のタグにも対応したHTMLパーサーです。

要素の取得などは非常に便利なのですが、

これもHTMLをパースするだけでJavascriptの実行はできません。

したがって、採用しませんでした。

HtmlUnit

NekoHtmlをHTMLパーサーとして利用したヘッドレスブラウザーです。

Javascriptの実行にRhinoを使っています。

僕自信の目的にはこれで十分な機能だったのですが、

残念ながら『冒険者の広場』を表示しようとしたら、

Javascriptエラーが発生したので、

採用しませんでした。

Selenium

言わずと知れたFireFoxの自動実行ツールです。

ブラウザの操作を覚えさせることも可能なため、

比較的スクリプトを作るのが楽です。

しかし、個人的にはブラウザーがポコポコ立ち上がるのが

好きでないので採用しませんでした。

JavaFXのWebEngine

レンダリングエンジンにWebKitを採用したブラウザーです。

Javascriptの実行にRhinoが使われています。

JavaFXのアプリケーション側からJavascriptの実行も指示することができます。

好みであれば画面を立ち上げることなしに実行できます。

特に文句をつけるところもないので採用しました。

JavaFXをヘッドレスブラウザーとして使う時のやり方

Applicationを継承したクラス

JavaFXのアプリケーションですので、Applicationクラスを継承したクラスを作成します。

DqxWebDriver.java
1
2
3
4
5
6
7
8
9
public class DqxWebDriver extends Application {
    private static WebEngine engine;
    private static final String URL = "…";
    @Override
    public void start(Stage stage) {
        engine = new WebEngine();
        engine.load(URL);
    }
}

ここでのポイントは2つです。

  • startメソッドの中でWebEngineのインスタンスを生成すること
  • 特に画面を表示する必要がないので、stage.show()を実行しないこと

JavaFXの起動と終了

Application.launch(Class<? extends Application>)を実行すればいいのですが、

これを実行するとJavaFXアプリケーションを実行している間は

何もできなくなるため、別のスレッドで実行する必要があります。

Bazaar.java
1
2
3
4
5
6
7
8
9
10
ExecutorService service = Executors.newFixedThreadPool(1);
service.submit(new Runnable(){
    @Override
    public void run() {
        Application.launch(DqxWebDriver.class);
    }
});
// Do some operation
Platform.exit();
service.shutdown();

JavaFXの終了にはPlatform.exit()を実行します。

これによりJavaアプリケーション自体が起動したままになるのを防ぐことができます。

Javascriptの実行

WebEngineexecutScript(String)を実行します。

DqxWebDriver.java
1
2
3
4
5
6
7
8
9
private static WebEngine engine;
public static void executeScript(
        final String script,
        final BlockingQueue<Object> queue) {
    Platform.runLater(new Runnable(){
        Object result = engine.executScript();
        queue.put(result);
    });
}

WebEngineの操作はJavaFXのApplication Threadから実行しなければなりません。

そのために、Platform.runLater(Runnable)を介して実行する必要があります。

引数はRunnableのために、戻り値を利用することができません。

そのために、BlockingQueue<T>を使っています。

WebEngine#executeScript(String)の戻り値をここではObjectとして利用していますが、

戻り値は実際には次のようにマッピングされます。

Javascriptでの型Javaでの型
booleanBoolean
number(整数値)Integer
number(少数値)Double
stringString
objectnetscape.javascript.JSObject
functionnetscape.javascript.JSObject
nullnull

Javascriptでの配列はobjectですので、JSObjectにマッピングされます。

JSObjectのあ使い方ですが、getMember(String)getSlot(int)を用いて値にアクセスします。

getMemberメソッド

{hoge : "hoge", foo : 10}というオブジェクトの場合、

次のようにgetMember(String)メソッドを使用して値を取り出します。

1
2
String hoge = (String)object.getMember("hoge");
int foo = (int)object.getMember("foo");

getSlotメソッド

[1,2,3,4,5]という配列の場合、

次のようにgetSlot(int)メソッドを用いて値を取り出します。

1
2
3
int one = (int)object.getSlot(0);
int two = (int)object.getSlot(1);
int five = (int)object.getSlot(4);

WebEngineの同期

先ほど書いたように、WebEngineへの操作は別スレッドで実行することになりますが、

さらにJavaFX ApplicationスレッドとWebEngineは異なるスレッドで実行されています。

したがって、WebEngineの動作に合わせてアプリケーションの実行をしたいシーンが発生すると思います。

その場合、getLoadWorkerメソッドでWorkerを取得して、getStateにて

WebEngineの状態を確認する必要があります。

DqxWebDriver.java
1
2
3
4
5
6
7
8
9
10
private static WebEngine engine;
public static Worker.State getEngineState() {
    final BlockingQueue<Worker.State> queue = new BlockingQueue<>();
    Platform.runLater(new Runnable(){
        @Override
        public void run() {
            queue.put(engine.getLoadWorker().getState());
        }
    });
}

この結果がWorker.State.RUNNINGの間は、

Javascriptの実行などをしているため、

戻り値を利用するなどのアプリケーション操作はできません。

したがって、Thread.sleep(long)等で停止しておくとよいでしょう。

DqxWebDriver.java
1
2
3
4
5
6
7
public static void waitForEngine() {
    Worker.State state = Worker.State.RUNNING;
    while(state.equals(Worker.State.RUNNING)) {
        Thread.sleep(100l);
        state = getEngineState();
    }
}

おわり

以上が昨日全くもって話しえなかったことです。

スレッド周りが非常に面倒な感じがしますが、

慣れればあとはJavascriptを好きに実行できるので、

WebアプリでのJavascriptのテストなどで活用できたりします。

超高速開発ツールに関していろいろと思ったこと

こんにちわ、みけです。

先月末は体調がすこぶる悪くて、ブログも何もしていませんでした。

超高速開発コミュニティ

さて、プログラマーの皆様にも、SIer諸氏の皆様にも

なんだか香ばしい話題が各種メディアから出ましたね。

また、設立にあたって関係者のブログが公開されています。

各種の反応

publickeyで取り上げられたから、プログラマーの皆様の反応も早く、

  • プログラマーの待遇を良くすれば開発速度が云々
  • 意思決定の速いマネージメントに変えれば云々
  • 無駄な成果物(エクセルファイル)を作らなければ云々

といったツイートが見られました。

また、これに便乗したセールをやっている輩もいるようです。

プログラマーの方のブログでも様々な意見があるようです。

僕自信の考え

まあ、反応を取り上げたところで、お前自身はどうなんだよという話になりますね。

僕としては、こういうツールは使える場面ではどんどん適用すればいいと思っています。

ただし、このツール類、多少のIT業界でのパラダイムシフトが必要だと思っています。

従来のITシステムを作るときの役割

ユーザー企業

アプリケーションへの要求
発注



アプリケーション
SIer

アプリケーション開発
選定



ソフトウェア
各種
ベンダー

ハード・ミドル・ソフトの提供

まあ、多少の異なるところはあれ、だいたいこの形になっていると思います。

このスキームでの超高速開発ツールの問題点は、

ツールの採用の如何がSIerにかかっているということです。

で、当のSIerにおいては社員の雇用を確保するという義務があったりする関係上、

(ここでは結構大きめなSIerを前提としています)

工数が少なくなる(人数を減らしてしまう)ような開発手法は採用できないため、

超高速開発ツールの採用を見合わせたりします。

もちろん、以下の様な理由もあったりするでしょう。

  • 超高速開発ツールに関するノウハウがない
  • フルスクラッチでしか成功経験がない
  • 社内の評価が売上高なので、人月で積み上げられる開発モデルが望ましい
  • フルスクラッチで開発して若手社員を教育したい

こういった、SIerの思惑にはユーザーの視点というものが欠けているように思われます。

まあ、実際に欠けているわけで、大枚をはたいて完成したシステムが使えないなんてことが

あったり、なかったり…

また、ユーザー企業としても、業務での情報の活用度を高めたいけど、

元が取れるのかどうかわからないものに、

100人月(1億円くらい?)を払っていいものかどうかがわからないなんてことも

あるかもしれません。

以上から、従来のスキームでユーザー企業はITを必要としていても、

ITの恩恵が受けられないみたいな状態になってしまうかもしれません。

なんとなく、企業とITに関してSIerがガンになっている気がしなくもないですね。

超高速開発コミュニティが気にしているのは、

この状態なのかもしれません。

超高速開発ツールを活用する場合の各業界の役割

で、超高速開発コミュニティが目指しているのは次のようなスキームなのではないかと思っています。

ユーザー企業

業務ツール作成
購入



ツール
ツール
ベンダー

ツールの提供

癌であるSIerは取り除いて、

ユーザー企業が超高速開発ツールを活用して、

自分たちの必要な物を作る。

これなら、ユーザー企業は

  • 自分たちの欲しいアプリケーション作れるし
  • すぐにアプリケーションを使えるし
  • プログラミング言語わかってなくても作れるし
  • 費用がそんなにかからないし

といいことづくめになるわけですね。

で、こういうところがおそらく超高速開発コミュニティの目指しているところ

であると僕は考えています。

(違うかもしれませんけどね)

お気づきのことだと思いますが、この図では、あえてSIerの枠を無くしています。

—– え、SIer、どうなっちゃうの?死ぬの?

—– はい、死んでください。

まあ、半分冗談ですが、半分本気です。

半分冗談と言ったのは、超高速開発ツールを提供するとはいえ、

単なるソフトウェア・ベンダーなので、顧客業務にどのツールが適しているかどうかを

判断するような役割の仕事は残っていると思います。

その部分にかろうじてSIerが残れると思います。

で、半分本気なところですが、

超高速開発コミュニティの発表後日談的位置づけのエントリー

超高速開発コミュニティ- 記者会見から二日目の状況 – ジャスミンソフト日記

に次のような記述があります。

記者発表の場で幹事の樋山さんから発言がありましたが、超高速開発を実現するという各社のツールは具体的にどういうもので、各ツールの違いは何か、ユーザー企業にとってどういうメリットがあるのか、という資料をまず、ご提供するようにします。11月の公開を目標としていますが、時間がかかる理由は「単に各社の製品パンフレットを一覧表にしてまとめたものには、しない。」というコンセプトを掲げているためです。ベンダー視点ではなく、ユーザー視点の資料とはどうあるべきか、この議論から入っていきます。これから正念場を迎えますが、1社単独ではつくれないような資料としてまとめることができれば、これは大きな価値をもつことになるだろうと考えています。

これ、言い換えればSIerに残された仕事である、

どういう業務にはどのツールが適しているかということの判断は、

超高速開発コミュニティにおまかせあれということですね。

ということで、SIerにあげる仕事なんてありません、ってな感じになると思います。

直感

なーんて、無責任なことをジャスミンティーを飲みながらテキトーに書いているわけですが、

ここで書いている意見は記事を読んだプログラマーとしての意見ではなく、

超高速開発ツールとして上げられているGeneXusというソフトを使ったことがある

という経験からの直感で書いています。

一応GeneXusの紹介(メリット)

GeneXusというツールですが、

データベースの項目の定義

  • カラム名
  • データ型(文字列、数値、日付、画像など)
  • 空の値を許容する/しない
  • 一意な値

を作成すれば、scaffoldが自動で作成されて、

デプロイボタンを押せばサーバーにデプロイされるというツールになっています。

もちろんscaffoldだけでなく、

自由に画面をつくって(WYSIWYGな画面エディターつき)、

どのデータのどの項目を表示・入力させるといった設定をするだけで、

任意の業務画面を作成出来ます。

また、どの権限のユーザーがどの画面から業務を開始することができて、

どの権限のユーザーがどの画面で業務内容を把握して承認させるといった

フローの作成もできますし、その通知メールを送信するといったことも

ツールに仕様を記述すれば可能です。

また、設計書とか仕様書の管理について、

「(最新版)◯◯画面仕様_20130809_2.xlsx」といったファイルを

「Y:¥共有¥10仕様書¥2013-08-09最新¥30△△業務」フォルダーで管理するとか

そんなくだらない煩わしいことからも開放されます。

すべての仕様はデータベースに管理されており、

常に最新の仕様を把握することが可能です。

(履歴はどうだったか忘れた…)

また、日本ではどうだか知りませんが、

海外、特にアメリカとベネズエラでは、

コミュニティ活動も活発で、

セキュリティイシューなどのパッチもすぐ提供されますし、

単なるWeb業務アプリケーションを超えて、

AndroidやiPhone、iPad向けの業務アプリケーションも作成出来ます。

一応GeneXusの紹介(デメリット)

まあ、ここまではGeneXusの研修を受けた時の、売り文句をそのまま書いただけです。

(あれ、守秘義務なんとか…まあ、いいか、業務情報でないし)

メリットばかりを書いていてはアンフェアなので、

デメリットも書いておきます。

プログラミングレスを謳っていますが、

独自の業務プロセスに適合させようとすると、

GeneXusの独自言語での開発が求められます。

一応、オブジェクト指向っぽい言語ですが、

VB6っぽい感じの言語であり、

オブジェクト指向な割にはエディターの補完が効かないので、

どのオブジェクトにはどういうメンバー・メソッドがあって、

どういう責務を果たしているといった

独自言語に対する知識が求められます。

また、ユーザーが云々と上では述べましたが、

ユーザー認証に関する機能は各自で作りこまないといけません。

その実装の参考例はコミュニティのページから参照できますが、

英語で書かれているうえ、なんか古いバージョンのツールでの方法だったりします。

また、サポートについて、各ベンダーに質問すると、

大体一週間以内に回答がもらえますが、

コミュニティのページで得られる情報の日本語訳であることが多く、

また、日本語が上手でないのか的はずれな回答をすることが多く、

あまり頼りにならなかったという印象があります。

また、ツールに関する各種情報もコミュニティのページで得ることができますが、

多くの情報がスペイン語(ベネズエラで開発されているので当然といえば当然)であり、

英語のものがなかなか手にはいらないことや、

日本語情報が希薄という点については気をつけておかなければならないところでしょう。

(ちなみに、僕の第二外国語はスペイン語なので特に不自由しませんでしたが…)

また、対応しているブラウザーがIEとFireFoxだけで、

ChromeとかOperaに対応していませんでした。

(当然、Mac OS Xも…)

また、グラフを扱う場合にはFlashが必要なようです。

まあ、これらの情報は数年前のものなので、

今は変わっているかもしれませんが…

研修の参加者

僕はSIerとしてベンダーが開催する研修(結構なお値段)に参加していたわけですが、

研修の他の参加者は、企業のIT部門の人ばかりでした。

なので、SIerがこのツールを使って、ユーザーにどうこうするというよりは、

企業がこのツールを使って自社のシステムをどうこうするという位置なのでしょう。

ベンダーの選定について

技術的な課題というのは大抵のプロジェクトで発生します。

その課題への対応としてArtech社(GeneXusの開発会社)に問い合わせる場合に、

スペイン語が必須になるので、ベンダーがスペイン語ができるかどうかというのは、

見極めのポイントになります。

(とはいえ、GeneXusを売っているベンダーなんて数社しかありませんが…)

あと、ちゃんとした日本語ができるベンダーであるというのも

見極めのポイントになります…(???)

超高速開発コミュニティが目指すべきところ

まあ、GeneXusを触ったことがあるという観点から、

思いつくままに書いてきたわけですが、まとめとか書いといた方が良い気がして行きました。

超高速開発ツールのターゲット

ITに大金(60人月=6,000万円)を投資できないけど、

ITを必要としていて、小額(1,000万円程度)なら投資できるといった企業は、

これらのツールの恩恵を預かるのがよいかもしれません。

したがって、超高速開発コミュニティのターゲットは、

SIerなどではなく、あくまで小さくてたくさんいるユーザーであると認識しています。

各ベンダーにはコミュニケーションが求められる

また、先ほどちゃんとした日本語を使えるベンダーという言い回しを使いました。

これは、ベンダーが単なるスペイン語の逐語訳を回答するのではなく、

ユーザー企業のIT担当者のレベルでコミュニケーションを取るという

これまでSIerが担っていた部分をベンダーが担わないと、

これらのツールは広まらないのではないかと思っています。

なお、「そういったユーザー企業との折衝はSIerが」ってなると、

また要件定義が云々、仕様書が云々となって元の木阿弥に戻ります。

超高速開発ツールに精通した技術者の育成

とはいえ、ベンダーが複数の企業とコミュニケーションを取るというのは、

現実的ではないので、

超高速開発ツールに習熟した企業等が開発の請負などをしてくるのではないかと思います。

で、その企業に勤めている技術者がツールに精通することが

超高速開発ツール普及のポイントでしょう。

技術者がツールに精通していくためには、

商用では使えないけど、個人で使う分には無料であるような

ツールの無償版が求められるでしょう。

また、そのツールを使って、

ちょっと小銭稼ぎしちゃった、(・ω<)テヘペロといった技術者が出てきても、

お咎めしないような寛容さがないとちょっと厳しいかなとおもいます。

ネガティブ情報も共有すること

ベンダーの提供するツールの情報というのは、たいてい成功事例ばっかりな気がします。

売り込みをかける分にはそれでよいかもしれませんが、

技術者の普及のためには、

本当に価値のある情報 = 失敗事例なども共有して貰いたいと思っています。

こういった情報を技術者が共有・討論・研究して、

ツールを開発している会社にフィードバックされないと、

何度も同じ失敗を繰り返すだろうし、

ツール自体も魅力的にならないと思っています。

本当の競合相手はSalesforce、Google

超高速開発といったところで、

単純にはあるものを再利用しようという考えなわけで、

より多くの業務ノウハウを持っている会社が本当の競合相手であると思っています。

したがって、SalesforceやGoogleといった会社が超高速開発ツールの

競合相手であると認識しています。

日本への浸透度という点でも超強力です。

本の出版数やコミュニティの勢いを指標に浸透度を比較しますが、

日本ではGeneXusに関する書籍は1冊しかありませんが、

SalesforceやGoogle Appsに関する本はたくさんあります。

SalesforceやGoogle Appsに関する本は駅前の本屋さんにおいてありますが、

GeneXusに関する本は新宿Book Firstとか紀伊国屋に行かないとありません。

Google Appsのコミュニティはユーザーレベルで作られていますし、

Salesforceは勉強会・カンファレンスを積極的に主催しています。

これらのコミュニティや勉強会への参加は無料だったりします。

この勢力と渡り合うためには、

コミュニティ自体がベンダーだけにとどまったものではなく、

ユーザー・デベロッパーに開かれたものであることが求められるでしょう。

参加費1万円とか5万円とか徴収している場合ではないと思っています。

以上。

P.S.

まあ、こういったことを昨日・一昨日と某イケメン氏と話したりしていました。

Groovyでantログの出力先を変更する

こんにちわ、みけです。

groovyでantのタスクでいろいろと大量のタスクを実行させている時に、

標準出力に出てくるログがちょっとうざったい時があります。

その場合に、ログの出力先のstreamを変更することができるようです。

1
2
3
4
5
6
7
8
9
10
11
// 出力先のstream
def x = new ByteArrayOutputStream()
// antの出力先を変更 
ant = new AntBuilder()
ant.project.buildListeners.each{
    it.outputPrintStream = new PrintStream(x)
}
// 大量のタスクを実行
(1..100).each {
    ant.echo "this is $it time log."
}

このスクリプトを実行すると次のような標準出力になります。

1
1..100

実際にはこのstreamを外部のファイルにしておくと後で実行結果だけを参照するなどできます。

GradleのsetupBuildタスクを試してみた

こんにちわ。みけです。

前回の記事、gradle1.7のリリースノート超意訳で、

いくつか気になる機能があったので試してみることにしました。


今回は、setupBuildタスクを試してみます。

リリースノートでは

  • javaプラグインが適用されたシンプルなビルドファイル
  • サンプルのプロダクションクラスとディレクトリー
  • サンプルのJUnitテストとディレクトリー
  • Gradle Wrapperファイル

が生成されるとありました。

では実際に試してみたいと思います。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
$ mkdir sample
$ cd sample/
$ ll
total 0
drwxr-xr-x  2 mike  staff    68B  7  9 11:19 .
drwxr-xr-x  3 mike  staff   102B  7  9 11:16 ..
$ gradle setupBuild --type java-library
:wrapper
:setupBuild

BUILD SUCCESSFUL

Total time: 6.362 secs
$ ll
total 40
drwxr-xr-x  9 mike  staff   306B  7  9 11:19 .
drwxr-xr-x  3 mike  staff   102B  7  9 11:16 ..
drwxr-xr-x  3 mike  staff   102B  7  9 11:19 .gradle
-rw-r--r--  1 mike  staff   1.2K  7  9 11:19 build.gradle
drwxr-xr-x  3 mike  staff   102B  7  9 11:19 gradle
-rwxr-xr-x  1 mike  staff   5.0K  7  9 11:19 gradlew
-rw-r--r--  1 mike  staff   2.3K  7  9 11:19 gradlew.bat
-rw-r--r--  1 mike  staff   645B  7  9 11:19 settings.gradle
drwxr-xr-x  4 mike  staff   136B  7  9 11:19 src

お、見事プロジェクトが作成されています。

また、wrapperタスクを実行した後の状態になっていることもわかります。

(gradleディレクトリー、gradlewファイルとgradlew.batファイルが生成されている)

では、build.gradleの中身を見てみたいと思います。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
$ cat build.gradle 
/*
 * This build file was auto generated by running the Gradle 'buildSetup' task
 * by 'mike' at '13/07/09 11:19' with Gradle 1.7-rc-1
 *
 * This generated file contains a sample Java project to get you started.
 * For more details take a look at the Java Quickstart chapter in the Gradle
 * user guide available at http://gradle.org/docs/1.7-rc-1/userguide/tutorial_java_projects.html
 */

// Apply the java plugin to add support for Java
apply plugin: 'java'

// In this section you declare where to find the dependencies of your project
repositories {
    // Use 'maven central' for resolving your dependencies.
    // You can declare any Maven/Ivy/file repository here.
    mavenCentral()
}

// In this section you declare the dependencies for your production and test code
dependencies {
    // The production code uses the SLF4J logging API at compile time
    compile 'org.slf4j:slf4j-api:1.7.5'

    // Declare the dependency for your favourite test framework you want to use in your tests.
    // TestNG is also supported by the Gradle Test task. Just change the
    // testCompile dependency to testCompile 'org.testng:testng:6.8.1' and add
    // 'test.useTestNG()' to your build script.
    testCompile "junit:junit:4.11"
}

作成されたbuild.gradleファイルでは

  • javaプラグインが適用されている
  • slf4jがコンパイル用のライブラリーとして登録されている
  • JUnitがテスト用のライブラリーとして登録されている

ようです。

また、サンプルのプロダクションコードとテストコードが生成されるということですが、

  • src/main/java/Library.java
  • src/test/java/LibraryTest.java

の二つのファイルが作成されています。

それぞれの中身は次のようになっています。

Library.java
1
2
3
4
5
6
7
8
9
10
11
/*
 * This Java source file was auto generated by running 'gradle buildSetup --type java-library'
 * by 'mike' at '13/07/09 11:19' with Gradle 1.7-rc-1
 *
 * @author mike, @date 13/07/09 11:19
 */
public class Library {
    public boolean someLibraryMethod() {
        return true;
    }
}
LibraryTest.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import org.junit.Test;
import static org.junit.Assert.*;

/*
 * This Java source file was auto generated by running 'gradle buildSetup --type java-library'
 * by 'mike' at '13/07/09 11:19' with Gradle 1.7-rc-1
 *
 * @author mike, @date 13/07/09 11:19
 */
public class LibraryTest {
    @Test public void testSomeLibraryMethod() {
        Library classUnderTest = new Library();
        assertTrue("someLibraryMethod should return 'true'", classUnderTest.someLibraryMethod());
    }
}

実際にこのコードを使うことはないと思いますが、

これまでmavenライクなディレクトリーを作るのが面倒だったので、

このsetupBuildタスクができたことはGradleユーザーには朗報ですね。

gradle1.7のリリースノート超意訳

こんにちわ、みけです。

現在、gradle1.7-rc1が利用できます。

リリースノートの超意訳をどうぞ。


Gradle Release Notes

Version 1.7-rc-1

Gradle1.7になるとすごく速くなります。dependency解決とビルドスクリプトのコンパイルの改善をしました。Gradleユーザーみんなこの恩恵に預かれますが、でっかいプロジェクトだと、その効果はもっと顕著になります。パフォーマンスの改善とスケービリティがGradle1.7の主要なテーマになっています。

これらの改善点に加えて、Gradle1.7では面白い機能がついてきます。finalizer taskメカニズムによってタスクの結果がどうであれ、タスクの次に別のタスクを起動させることができるようになります。例えば、アプリケーションサーバーを起動するようなintegrationテスト(の失敗)の後にアプリケーションサーバーを終了させることができるようになります。コピーやアーカイブ生成時にファイルの重複をコントロールする機能が登場します。

Gradle1.7のBuild Setupプラグインの改善によりプロジェクトをテンプレートから生成する機能が利用可能になります。これにより新規プロジェクトの作成が簡単になります。

C++からネイティブバイナリーを作成する機能も進化します。ネイティブバイナリーの生成に関しては結構面倒な領域ですが、今後もGradleのこの分野での進化を期待して下さい。

Gradle1.7ではコア・テベロップチーム以外からのコントリビュートが多いのも特徴です。Gradle1.7に貢献してくださったデベロッパーの皆様に感謝しております。

Table Of Contents

新機能や追加機能

修正された問題

非推奨となったもの

潜在的な互換性に関わる変更

コントリビューター

既知の問題


新機能や追加機能

より速いGradleビルド

Gradle1.7でのビルドはより速くなります。

  • dependency解決が速くなります(ほとんどのビルドで改善効果が現れます)
  • テスト実行が高速化されます(特にログを大量に出力しているようなGradleで顕著です)
  • ビルドスクリプトのコンパイルが速くなります(Gradle1.6に比べて75%の時間でできるようになります)
  • 並行実行モードが高速になります

Finalizer tasks incubating

Gradle1.7から新しいタスク実行ルールが導入され、タスク終了時に他のタスクを起動することができるようになります。この機能はMarcin Erdmann氏によるものです。

Finalizer tasksではタスク終了時にそのタスクの結果にかかわらず別のタスクを起動します。

1
2
3
4
configure([integTest1, integTest2]) {
    dependsOn startAppServer
    finalizedBy stopAppServer
}

この例ではintegTest1タスクとintegTest2タスクの終了時にstopAppServerタスクを実行するように宣言されています。どちらか片方のタスクがビルドの最中に起動された場合もfinalizer taskが自動で実行されます。ビルドの最中に両方のタスクが起動された場合でも必ず両方のタスクの最後にfinalizer taskが実行されます。finalizer taskのstopAppServerはGradle実行時に起動タスクとして指定する必要はありません。

C++プロジェクトサポートの改善 incubating

GradleはC++プロジェクトのサポートをしていました。これはGradleをネイティブコードプロジェクトのビルドツールとして最良のものにする改善になります。

  • スタティックライブラリーの作成、リンク作成
  • 異なるC++ツールチェインでも利用可能になります(Visual C++、GCCなど)
  • 異なるアーキテクチャー、ビルドタイプ、OSへの対応
  • Variantに基づいた依存性解決
  • その他もろもろ(詳しくはこちらを見て下さい(英語))

JCenter レポジトリーのサポート incubating

Bintray’s JCenter Repositoryからdependencyを取得できるようになります。jcenter()レポジトリーノーテーションにより利用可能です。JCenterはコミュニティリポジトリーで、Bintrayから無料で配布可能です。

1
2
3
repositories {
    jcenter()
}

このスクリプトによりhttp://jcenter.bintray.comがApache Maven repositoryと同様にリポジトリーリストに追加されます。

パターン・ベース・ファイル・コピー設定 incubating

Gradle1.7ではきめ細かい設定によりどのファイルがコピーされるべきかを定義することができます。設定方法は”Ant Patterns”のように指定出来ます。この機能はKyle Marhan氏によるものです。

GradleにはファイルコピーのAPI(CopySpec)があり、アーカイブすることもできます。この新しい機能はより強力なものとなります。

1
2
3
4
5
6
7
8
task copyFiles(type : Copy) {
    from 'src/files'
    into "$buildDir/copied-files"
    // Replace the version number variable in only the text files
    filesMaching('**/*.txt') {
        expand version: '1.0'
    }
}

fileMatchingメソッドではClosureを引数に取り、FileCopyDetailsオブジェクトの設定を行うことができます。これと反対の動作をするfilesNotMatchingというメソッドもあり、パターンに該当しないファイルすべてを指定することもできます。

ファイル重複時のハンドリング機能 incubating

ファイルのコピーやアーカイブするときに、よく重複することがあります。そのような重複が発生した場合の処理方法を設定できるようになります。

1
2
3
4
5
task zip(type : Zip) {
    from 'dir1'
    from 'dir2'
    duplicatesStrategy 'exclude'
}

処理方法には2つあります。includeexcludeです。

includeストラテジーの場合、既存のGradleの動作と変わりありません。後からコピーされたもので上書きされます。なお、実際に発生した場合には警告が出力されるようになります。アーカイブ(zip、jar)の場合は新しいものが作成されます。

excludeストラテジーの場合、重複したファイルは無視されます。最初にコピーされたファイルが最終的に使われ、その後のファイルは無視され続けます。アーカイブの場合も同様で、最初に作成されたら、同じファイル名のものは作成されません。

1
2
3
4
5
6
7
8
9
10
11
task zip (type : Zip) {
    duplicatesStrategy 'exclude' // default strategy
    from ('dir1') {
        filesMatching('**/*.xml') {
            duplicatesStrategy 'include'
        }
    }
    from ('dir2') {
        duplicatesStrategy 'include'
    }
}

Gradle Wrapperはビルドスクリプトを特にいじらなくても利用可能になります incubating

Gradle WrapperWrapperタスクを定義しなくても利用できるようになります。つまりWrapperのためにビルドスクリプトをいじることはありません。

Wrapperを利用するためには単純に次のコマンドを実行するだけです。

1
$ gradle wrapper

Wrapperファイルがこれにより生成・設定されて、現在使用しているGradleのバージョンを利用することができるようになります。

なお、wrapperタスクをカスタマイズしたい場合は、ビルドスクリプトを次のように変更します。

1
2
3
wrapper {
    gradleVersion '1.6'
}

もし既存のWrapperタイプのタスクがある場合は、そちらが使われます。それ以外の場合はデフォルトのwrapperタスクが使用されます。

Javaライブラリーテンプレートプロジェクトの生成

build-setupプラグインがプロジェクトタイプをサポートするようになりました。Gradle1.7ではjava-libraryタイプが利用可能です。このタイプで生成されるのは以下のとおりです。

  • javaプラグインが適用されたシンプルなビルドファイル
  • サンプルのプロダクションクラスとディレクトリー
  • サンプルのJUnitテストとディレクトリー
  • Gradle Wrapperファイル

Javaライブラリープロジェクトを作る場合は次のコマンドを実行するだけです(build.gradleファイルは必要ありません)。

1
$ gradle setupBuild --type java-library

詳しくはこちらをBuild Setup pluginを参照して下さい。

publicationのカスタマイズ – new publishingプラグインにおける incubating

publishプラグインでアーカイブを発行するときにartifactの情報を明示的にカスタマイズできるようになりました。以前はartifactの情報はプロジェクトの情報から取得されていました。

MavenPublicationではgroupIdartifactIdversion情報を設定できるようになります。Mavenのpompackagingの値も設定可能です。

1
2
3
4
5
6
7
8
9
publications {
    mavenPub(MaenPublication) {
        from components.java
        groupId 'my.group.id'
        artifactId 'my-publication'
        version '3.1'
        pom.packaging 'pom'
    }
}

IvyPublicationではorganisationmodulerevisionを設定出来ます。IvyModuleDescriptorstatusの値も設定可能です。

1
2
3
4
5
6
7
8
9
publications {
    ivyPub(IvyPublication) {
        from components.java
        organisation 'my.org'
        module 'my-module'
        revision '3'
        descriptor.status 'milestone'
    }
}

この機能のすごいところは、moduleartifactIdを設定することができることです。というのも、これまではproject.nameを使っており、Gradleのビルドスクリプトで変更することができなかったためです。

複数のモジュールを一つのGradleプロジェクトから発行する incubating

publishプラグインでは複数のモジュールを一つのGradleプロジェクトから発行することが可能になります。

1
2
3
4
5
6
7
8
9
10
11
12
13
project.group 'org.cool.library'
publications {
    implJar(MavenPublication) {
        artifactId 'cool-library'
        version '3.1'
        artifact jar
    }
    apiJar(MavenPublication) {
        artifactId 'cool-library-api'
        version '3'
        artifact apiJar
    }
}

これまでも可能でしたが、ここに示したような簡単な方法ではできませんでした。新しいivy-publishmaven-publishプラグインでは簡単に出来ます。

TestNGパラメーターがテストレポートに出力されます incubating

TestNGではparameterizing test methodsによってあるテストを複数回、異なるデータの入力でテストを行うことがサポートされています。以前のGradleのレポートでは、パラメタライズドテストは複数行にわたってレポートが出力されていましたが、そのテストの区別をすることができませんでした。テストレポートにはパラメーターのtoString()メソッドによる値を出力できるようにし、どのデータによるテストかを区別できるようにしました。

ParameterizedTest.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import org.testng.annotations.*;

public class ParameterizedTest {
    @Test(dataProvider = "1")
    public void aParameterizedTestCase(String var1, String var2) {
        // do testing
    }

    @DataProvider(name = "1")
    public Object[][] provider1() {
        return new Object[][] {
           {"1", "2"},
           {"3", "4"}
        };
    }
}

上記のテストに対するレポートは次のように表示されます。

  • aParameterizedTestCase(1, 2)
  • aParameterizedTestCase(3, 4)

この情報はGradleのHTMLテストレポートとJUnit XMLファイルに反映されます。JUnit XMLファイルは一般的にテストの実行結果をCIサーバーに引き渡す役割を担っており、CIサーバーでパラメーターの情報を参照することが可能になります。

テストタスクに標準的なレポートインターフェースを採用

レポートインターフェースによりレポートをコントロールする方法が提供されます。テストタスクではこのレポートインターフェースが採用されます。

1
2
3
4
5
6
7
apply plugin: 'java'
test {
    reports {
        html.enabled = false
        junitXml.destination = file("$buildDir/junit-xml")
    }
}

testタスクの提供するTestReports型のReportContainerを通じて、HTMLによるテストレポートおよびJUnit XMLファイルの出力の制御を行えるようになります(これらのファイルは通常CIサーバーやその他のツールとテスト結果を共有するために使われます)。

これによりテストタスクも他のレポート生成タスクとAPIレベルで同等になります。また、不要であればJUnit XMLファイルを生成しないことも可能です。

ビルドダッシュボードの改善 incubating

上記の変更(テストタスクに標準的なレポートインターフェースを採用)は言い換えると、テストレポートがビルドダッシュボードに現れるということです。

なお、buildDashbordタスクは自動的にレポート系タスクと同時実行されます(Finalizerタスクの機能により実現されています)。

JUnit XMLファイルでテストケースごとにテストの出力を表示するようになりました incubating

この変更によりJenkinsなどのCIサーバーでよりよいテスト結果を得ることができます。

JUnit XMLファイルはテスト結果のフォーマットとしてデファクト・スタンダードです。たいていのCIサーバーはこのファイルをテストの実行結果として使っています。元々は「JUnit And Tasks」によって考えられたもので、JUnitとほぼ同時期に作られ、多くの局面で使われてきましたが、特に仕様が定められていませんでした。

このファイルにはテスト中の標準出力(System.outSystem.err)の内容が収められています。これまでは出力はクラスレベルでのみ記録されていました。つまり、テストケース毎の出力が得られなかったわけです。しかしGradleでは「テストケース毎の出力」モードを利用することができるようになりました。

1
2
3
4
5
test {
    reports {
        junitXml.outputPerTestCase = true
    }
}

このモードを使うと、XMLレポートはテストケースごとに作成されます。Jenkins CIサーバーではテストケース毎の結果を参照できるようになります。outputPerTestCase = trueと設定しておくと、テストケース毎の出力が画面に表示されます。以前はテストクラス毎の出力でした。

JenkinsのJUnit Attachments Pluginはこの機能とともに用いることで有効活用できます。

ApplicationプラグインでJVMパラメーターを指定できるようになります incubating

Olaf KilschatによりApplication PluginはデフォルトのJVM引数を設定することができるようになります。

1
2
apply plugin: 'application'
applicationDefaultJvmArgs = ['-Dfile.encoding=UTF-8']

BndライブラリーのアップデートによりOSGiサポートが改善されました

OSGiプラグインはBndツールを使ってbundle manifestsを作成しています。Bndツールのバージョンが1.50.0から2.1.0に変更されます。

最も重要な変更点は”invokedynamic”命令を使うJavaコード用のmanifestが正確になることです。

修正された問題

Gradle1.7で30の問題が修正されました。

  • [GRADLE-642] – scala toolsまたはライブラリーが登録されていない場合にscalaタスクから出力されるエラーメッセージを修正
  • [GRADLE-1289] – “create-project”コマンドによるスケルトンプロジェクト作成機能を提供
  • [GRADLE-1372] – wrapperタスクをビルトインタスクにしました
  • [GRADLE-1387] – Gradle Architypes機能を提供
  • [GRADLE-1456] – Application PluginにてJAVA_OPTS/APPL_OPTSを設定する方法を提供
  • [GRADLE-1551]http://www.gradle.org/build_lifecycle.html%E3%81%AEtypo%E3%82%92%E4%BF%AE%E6%AD%A3
  • [GRADLE-1583] – Gradleがivy configurationの表現をサポートしていない
  • [GRADLE-1704] – “gradle —version”コマンドの日付フォーマットの修正
  • [GRADLE-1742] – CodeNarcの警告数の上限を設定できるようにした
  • [GRADLE-2171] – zipファイル生成する場合に重複を回避するオプションを提供
  • [GRADLE-2519]testReport = falseと指定していてもテストFAILURE時に存在しないファイルのURLが表示される
  • [GRADLE-2666] – maven2GradleでNullpointerが発生
  • [GRADLE-2702] – testRuntime/testCompile configurationがテストがなくても解決される
  • [GRADLE-2738] – dependenciesで同じartifactの複数のバージョンを指定している場合、一番古いバージョンで解決される
  • [GRADLE-2752] – 自分自身のライブラリーの古いバージョンに依存している場合に
  • [GRADLE-2760]--parallel-threadsが内部的に4つまでしか使えない
  • [GRADLE-2765] – XMLテストレポートを出力できない設定を追加
  • [GRADLE-2766] – Ivy defaultConfMappingが解決に用いられない
  • [GRADLE-2780] – Gradle1.6でスクリプトのコンパイルが劣化
  • [GRADLE-2790] – Ivy dependency configuration mapping '*->@' がターゲットモジュールからすべてのconfigurationを含んでしまう
  • [GRADLE-2791] – Ivy dependency configuration mapping で左項'%'が無視される
  • [GRADLE-2792] – Ivy dependency configuration mapping で左項'*,!A'がAの解決時に含まれてしまう
  • [GRADLE-2793] – Ivy dependency configuration mapping で右項'#'が不適切なターゲットconfigurationに解される
  • [GRADLE-2802] – Wrapperのインストール/ダウンロードが-g--gradle-user-homeコマンドラインフラグを考慮しない
  • [GRADLE-2806] – OSGiプラグインにBNDライブラリーの最新版を適用
  • [GRADLE-2808]setting options.forkOptions.executableと設定をしてjoint compileするとNotSerializableExceptionが発生する
  • [GRADLE-2813]project.exec()でタスクを定義した場合、DSLが反映されない
  • [GRADLE-2815]@Inputboolean型のgetterでis*という名前のメソッドには適用されない
  • [GRADLE-2821] – テストのないテストタスクのレポート出力時にTestReportタスクがエラーを出力
  • [GRADLE-2825]strategyincludeに設定されていてもファイル重複の警告が出力される

非推奨となったもの

Gradleの進化にともなって置き換えられたものや無駄になったものは非推奨になり、次のメジャーバージョン(Gradle2.0)にて廃止されます。ユーザーガイドのFeature Lifecycleを参照して下さい。

以下に示すのが今回非推奨となったものです。もし何かあればGradle Forumsで問題提起してください。

テストレポートプロパティ

TestタスクがReportingインターフェースを実装するようになりました。現状のレポート出力に関するAPIは非推奨になりました。

  • disableTestReport()
  • enableTestReport()
  • isTestReport()
  • setTestReport()
  • getTestReportDir()
  • setTestReportDir()
  • getTestResultsDir()
  • setTestResultsDir()

すべての非推奨となった機能は新しいReporting機能でも利用可能ではあります。

潜在的な互換性に関わる変更

インメモリーdependencyキャッシング

パフォーマンスの改善により、Gradleはdependencyの記述をビルド全般にわたってメモリーにキャッシュするようになりました。つまりビルドの最中にdependencyメタデータが変わると、その変更はGradleは検知されない場合があります。このようなことが発生するパターンは確かめられていませんが、理論上想定することができます。

詳しくはFaster Gradle Buildsを参照下さい。

incubatingのJaCoCoプラグインの変更

JaCoCoカバレージプラグインのいくつかのクラスのプロパティの名前が適切なものに置き換わりました。

  • JaCoCoTaskExtension.destPathdestinationFileになりました。
  • JaCoCoTaskExtension.classDumpPathclassDumpFileになりました。
  • JaCoCoMerge.destFiledestinationFileになりました。

incubatingのbuild-setupプラグインの変更

ConvertMaven2GradleGenerateBuildScriptGenerateSettingsScriptクラスが削除されました。それらはSetupBuild型のbuildSetupタスクの一部となっています。

プラグインはbuild-setupタイプに基づいて異なるタイプ・名前のタスクを作成します。

setupWrapperタスクはwrapperとなっています。

incubatingのivy-publishプラグインにおけるタスク名の変更

maven-publishプラグインとの統一性を計るため、IvyPublicationでivy.xmlを生成するタスクが変わりました。タスクの名前はgenerateDescriptorFileFor${publication.name}Publicationとなります。

IvyPublicationのデフォルトstatus値がintegrationとなり、project.statusでなくなりました incubating

Ivy publicationモデルからGradleプロジェクトモデルを分離するために、ivy-publishプラグインで発行する際にproject.statusの値は使われなくなりました。

IvyPublicationIvyModuleDescriptor用にstatusの値が設定されていない場合、デフォルトのivyステータス('integration')が使われます。以前はproject.statusのデフォルト値である'release'が使われていました。

C++サポートの大幅な変更

GradleのincubatingなC++サポートがメジャーアップデートします。多くのプラグイン、タスク、APIクラス、DSLが変わります。この変更に伴いほとんどのシンプルなC++ビルドを変更する必要があります。

既存のC++ビルドを今後もGradleで実行するための対策は2つあります。

  1. Gradleを1.6のままにして、C++のサポートが安定するのを待ちます。その後、更新します。
  2. 最新の変更にビルドを合わせるようにします。なお、新たにリリースされた場合には変更が発生する可能性があります。

ConfigureableReportConfigurableReportに名称が変わりました

incubatingのorg.gradle.api.reporting.ConfigureableReportクラスはorg.gradle.api.reporting.ConfigurableReportクラスに変更されます。ミススペルが原因です。

テストがない場合テストタスクがスキップされるようになりました

テストがない場合には、テストタスクはスキップされるようになります。GRADLE-2702

以前はテストがない場合であっても、テストが実行されていました。その結果、dependencyの解決が実行され、何もないテストレポートが出力されていました。この変更によりビルドが高速になります。また、既存のビルドに対して大きな影響は発生しません。

OSGiプラグインに使われているBndライブラリーがアップデートされました

OSGiプラグインでbundle manifestの生成に使われていたBndツールのバージョンが1.50.0から2.1.0に上がります。

この変更は重要なアップグレードですが、後方互換性があります。

コントリビューター

Gradleコミュニティの他に、Gradleのディベロップメントチームは下記の人達にこのバージョンのGradleへの貢献に感謝します。

Marchin Erdmann
  • Finalizer tasks
Dan Stine
  • CodeNrcプラグインのmaxPriorityViolations setting (GRADLE-1742)
  • ユーザーガイドの修正
Kyle Mahan
  • アーカイブ・コピー操作における重複への対処(GRADLE-2171)
  • パターン・ベース・ファイル・コピー設定
Robert Kühne
  • ユーザーガイドのスペルミス修正
Björn Kautler
  • Build Dashboard のサンプルの修正
Seth Goings
  • ユーザーガイドの修正
Scott Bennett-McLeish
  • ユーザーガイドの修正
Wujek Srujek
  • wrapperのインストールロケーションに関する-gコマンドラインオプションの処理(GRADLE-2802)
Guillaume Laforge
  • OSGiプラグインのBndライブラリーアップデート(GRADLE-2806)
Yoav Landman

既知の問題

今のところGradle1.7に問題は見つかっていません。

JavaFXをヘッドレスブラウザーとして使うための基本テク #javafx

こんにちわ、みけです。

JavaFXをヘッドレスブラウザーとして使おうとして、

いろいろとWebのリダイレクトにハマりまくっています。

で、今回はJavaFXをJavaFXのスレッド以外のスレッドから扱うTipsを集めました。

Goal

期待していいこと

  • JavaFXの操作をJavaFXの外からする方法

期待してはいけないこと

  • GUIプログラミングの云々かんぬん

Basics

JavaFXのアプリケーションのサンプル的な起動方法は次のとおりです。

SampleApplication.java
1
2
3
4
5
6
7
8
9
10
11
12
import javafx.application.Application;
import javafx.stage.Stage;
// この中のmainメソッドからアプリケーションを起動しちゃう
public class SampleApplication extends Application {
    public static void main(String... args) {
        Application.launch(SampleApplication.class);
    }
    @Override
    public void start(Stage stage) throws Exception {
        // do something.
    }
}

さて、この方法を採用している限りにおいては、

JavaFXのスレッドがどうのこうのでハマることはありません。

今回のテーマはJavaFXアプリケーションの外と内をわけることにあります。

JavaFXアプリケーションはバックグラウンドで実行します。

そのためにはjava.util.concurrent.ExecutorServiceを用います。

ApplicationLauncher.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import javafx.application.Application;
import javafx.application.Platform;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
// 先ほどのSampleApplicationを起動します。
public class ApplicationLauncher {
    private static final ExecutorService SERVICE =
            Executors.newFixedThreadPool(1);
    public static void main(String... args) {
        // launch application
        SERVICE.submit(new Runnable(){
            @Override
            public void run() {
                Application.launch(SampleApplication.class);
            }
        });
        // do something
        // shutdown application
        Platform.exit();
        SERVICE.shutdown();
    }
}

Platform#RunLater(java.lang.Runnable)

JavaFX上でのみ動作するオブジェクトへの操作はPlatform#runLater(java.lang.Runnable)

通じて行います。

ControllerStimulation.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import javafx.application.Platform;
public class ControllerStimulation {
    // JavaFXのcontrollerクラスのインスタンス
    private SampleController controller;
    // Platform#runLater(Runnable)を通じてcontrollerクラスへの操作を行う。
    public void simulateClickButton () {
        Platform.runLater(new SimulateClickButton());
    }
    // 実際にcontrollerクラスを操作するRunnable
    private class SimulateClickButton implements Runnable {
        @Override
        public void run () {
            controller.clickButton(null);
        }
    }
}

戻り値を利用したい場合

御存知の通り、java.lang.Runnable#run()は値を返しませんので、

値の受け渡しにはjava.util.concurrent.BlockingQueue<T>を使うことになります。

Platform#runLaterjava.util.concurrent.Callable<T>を引数にとって、

java.util.concurrent.Future<T>を返してくれるといいんですけどね…

JavascriptExecution.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import javafx.application.Platform;
import javafx.scene.web.WebEngine;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
public class JavascriptExecution {
    // controllerクラスのインスタンス
    private SampleController controller;
    public String callJavascript(String javascript) {
        final BlockingQueue<String> queue = new LinkedBlockingQueue<>();
        Platform.runLater(new CallJavascript(queue, javascript));
        try {
            String result = queue.take();
            return result;
        } catch(InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
    private class CallJavascript implements Runnable {
        private final BlockingQueue<String> queue;
        private final String script;
        CallJavascript(final BlockingQueue<String> queue, String script) {
            this.queue = queue;
            this.script = script;
        }
        @Override
        public void run() {
            WebEngine engine = controller.getEngine();
            String result = (String) engine.executeScript(script);
            try {
                queue.put(result);
            } catch(InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

実際はJavascriptなどでエラーがあると、netscape.javascript.JSExceptionがthrowされるので、

Platform#runLater(java.lang.Runnable)java.lang.RuntimeExceptionを検知して、

Applicatoinを終了させるような仕組みを組み込んでおくことが望まれるのですが、

それはここでの話を逸脱するし、誰得な気がするので、

やめておく…

以下、実際に僕が書いているコード

JavaFXをheadless browserとして使うためのJSObjectの扱い方 - 1

こんにちわ。

みけです。

JavaFXでjavascriptのテスティングフレームワークを作ろうと思ってから、

早1年半。

全然成果があがっていません。

今日は、そんな自分のための俺得なエントリー。

Goal

期待していいこと

  • JSObjectでのarrayの取り扱い

期待できないこと

  • JavaFXのスレッドの同期方法
  • JSObjectでのobjectの取り扱い

JSObjectの扱い方 – array編

JSObject#getSlot(int)を使ってarrayの要素を取得します。

1
2
3
4
5
6
7
8
9
final String script = "(function(){return ['a', 2, 'c', 4, 'e'];})()";
final BlockingQueue<JSObject> queue = new LinkedBlockingQueue<>();
Platform.runLater(() -> {
    queue.put((JSObject) webEngine.executeScript(script));
});
JSObject array = queue.take();
for (int i = 0; i < 5; i++) {
    System.out.println(i + " -> " + array.getSlot(i));
}

結果は次のようになります。

1
2
3
4
5
0 -> a
1 -> 2
2 -> c
3 -> 4
4 -> e

ちなみに要素のindexより多い数をJSObject#getSLot(int)の引数に渡すと、

Stringundefinedが返ってきます。

第一回渋谷javaに行ってきた

第一回渋谷javaというのに行って来ました。

javaの勉強会としては比較的若い人の集まった勉強会だったと思います。


自己紹介で「好きなEclipseのショートカットは?」とか聞かれたので、

相変わらずのことで「Eclipse氏ね」と答えて来ました。

後、Eclipseの代替するエディターがvimだったので、

いや、IntelliJ IDEAですからとも自己紹介しておきました。


一応タイトルはネタっぽいですが、

真面目な話をして来ました。


他の参加者のブログがいくつかあるようです。

あと、いくつかスライドがアップロードされています。

JavaとOSS – ユーザーサイドから語ってみる


今回は初回ということでLTだけでしたが、ハンズオンなども実施していくとのことです。

Eclipse強制されるのでなければ、行こうかなと思います。

Java8 Lambda式を使ってみた

こんにちわ、みけです。

Java8初心者勉強会というのを開催してみました。

参加者2人でした。

Java8はだれも興味ないんだなと思いました。

Java8のラムダ式

特にもうこれといって目新しいこともありません。

メソッドが一つだけのインターフェースを記述するときに、

非常に記述が楽になるというものです。

例えば次のようなクラスがあるとします。

Item.jara
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Item {
    final String name;
    final int price;
    public Item(String name, int price) {
        this.name = name;
        this.price = price;
    }
    public String getName() {
        return name;
    }
    public int getPrice() {
        return price;
    }
}

上記のItemクラスをpriceの昇順、nameの昇順でソートするコードは以下のようになります。

1
2
3
4
5
6
7
List<Item> items = getItemList();
items.sort((left, right) -> {
    int priceOrder = left.getPrice() - right.getPrice();
    int nameOrder = left.getName().
            compareTo(right.getNmae());
    return priceOrder != 0? priceOrder : nameOrder;
});

ところで、Itemクラスのpriceだとかnameだとかについて、

それをソートするという操作は別に外のクラスが実装しても構わないけど、

Itemクラスが持っている方が何かと便利です。

したがって、オーダーするにあたって、Itemクラスに次のようなメソッドを

持たせるようにします。

Item.java
1
2
3
4
5
6
7
8
9
10
public class Item {
    final String name;
    final int price;
    // 途中省略
    public int comparePriceAscNameAsc (Item that) {
        int priceOrder = this.price - that.price;
        return priceOrder != 0 priceOrder :
                this.name.compareTo(that.name);
    }
}

先ほどのソートをするコードのラムダ式部分は非常に簡単化されます。

1
items.sort((left, right) -> left.comparePriceAscNameAsc(right));

ところで、呼び出されるメソッドはこのケースの場合わかりきっているので、

TODO : この記述は適当に書いているのでドキュメントを読み直します

Method Referenceに変更することが可能です。

1
items.sort(Item::comparePriceAscNameAsc)

という感じで、ラムダ式っぽい記述はなくなりました。

ちなみにMethod and Constructor ReferenceはLambdaの仕様の一部です。

How to Publish Artifacts to Maven Central Repository via Gradle Maven-publish Plugin (Version 1.6)

Gradle maven-publish plugin provides the easier way to publish artifacts than the old maven plugin.

This post introduces you the way to publish artifacts with maven-publish plugin.

Please Note that maven-publish plugin is incubating feature. Its DSL may change later.

Goal

After reading this post, you can upload your artifacts to maven central repository via maven-publish plugin.

Basics and Example

To publish artifacts you should do these things.

  1. to declare applying maven-publish plugin.
  2. to tell gradle which files should be published.
  3. to tell gradle where to upload artifacts.

Now let’s see sample build script.

build.gradle
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// declaration of plugins (1)
['java', 'maven-publish'].each {
    apply plugin : it
}
group = 'com.yourdomain'
version = '1.0'
repositories {
    mavenCentral ()
}
dependencies {
    compile 'org.apache.commons:commons-lang3:3.1'
    testCompile 'junit:junit:4.11'
}
publishing {
    publications {
        myPublication(MavenPublication) {
            // telling gradle to publish project's jar archive (2)
            from components.java
            // telling gradle to publish README file (2)
            artifact ('README.txt') {
                classifier = 'README'
                extension  = 'txt'
            }
        }
    }
    // telling gradle to publish artifact to local directory (3)
    repositories {
        maven {
            url "file:/${project.projectDir}/artifacts"
        }
    }
}

With this script you can publish your artifact via this command.

build.gradle
1
$ gradle publish

Then you will find some file is generated at artifact directory.

These files are …

  • sample-project.jar
  • sample-project.jar.md5
  • sample-project.jar.sha1
  • sample-project.pom
  • sample-project.pom.md5
  • sample-project.pom.sha1
  • sample-project-README.txt
  • sample-project-README.txt.md5
  • sample-project-README.txt.sha1

Conventions

maven-publish plugin has some conventions.

  • base archive name is project name.
  • classifier is given after the project name.
  • extension is given after the project name and classifier.
  • classifier and extension should be unique in all artifacts in a publication.

Publishing javadoc and source code as jar

javadoc

Following shows the way to publish javadoc as jar.

  1. call javadoc task.
  2. create a task of zipping javadoc and call it.
  3. give th zipping task to artifact method in publication container.
build.gradle
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// (2)
task javadocJar (type: Jar, dependsOn: javadoc) { // (1)
    classifier = 'javadoc'
    from javadoc.destinationDir
}
publishing {
    publications {
        myPublication(MavenPublication) {
            artifact (javadocJar) { // (3)
                classifier = 'javadoc'
            }
        }
    }
}

source codes

Following shows the way to publish source as jar.

  1. create a task of zipping sources as jar.
  2. give the zipping task to artifact method in publication container.
build.gradle
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// (1)
task sourceJar (type : Jar) {
    classifier = 'sources'
    from sourceSets.main.allSource
}
publishing {
    publications {
        myPublication(MavenPublication) {
            artifact (sourceJar) { // (2)
                classifier = 'sources'
            }
        }
    }
}

Modifying POM

requirements

maven-publish plugin generates POM, but it lacks some elements required by Sonatype OSS repository. Folowing shows list of elements to be added.

  • <name> – the name of project
  • <description> – the description for the project
  • <url> – project’s url
  • <scm><url> – repository url.
  • <scm><connection> – repository url for scm tool. for example using git – github, it becomes scm:git:git://github.com/your-name/project-name.git
  • <scm><developerConnection> – repository url for scm tool via ssh. for example using git – github, it becomes scm:git:ssh:git@github.com:your-name/project-name.git
  • <licenses><license><name> – license name (i.e. The Apache Software License, Version 2.0 etc…). In the case of the project being licensed under multiple license, licenses elements can have multiple <license> elements.
  • <licenses><license><url> – license url (e.x. if the project is licensed under Apache version 2, it becomes http://www.apache.org/license/LICENSE-2.0.txt)
  • <licenses><license><distribution>repo
  • <developers><developer><id> – developer’s id. If there are more than one developers, you can write <developer> elements more than one times.
  • <developers><developer><name> – developer’s name.
  • <developers><developer><email> – developer’s email.

build script

To add these elements to POM, you can acces pom file via pom object’s withXml method in MavenPublication container.

build.gradle
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
publishing {
    publications {
        myPublication (MavenPublication) {
            from components.java
            pom.withXml {
                asNode().children().last() + {
                    resolveStrategy = Closure.DELEGATE_FIRST
                    name 'project-name'
                    description 'description for project'
                    url projectUrl
                    scm {
                        url scmUrl
                        connection connectionUrl
                        developerConnection developerConnectionUrl
                    }
                    licenses {
                        license {
                            name 'The Apache Software License, Version 2.0'
                            url 'http://www.apache.org/license/LICENSE-2.0.txt'
                            distribution 'repo'
                        }
                    }
                    developers {
                        developer {
                            id 'your id or nick name'
                            name 'Your Name'
                            email 'your@mail.address'
                        }
                    }
                }
            }
        }
    }
}

Signing Jar

To keep quality of maven central repo, signing files is required.

These files should be signed.

  • main jar (file name is project-name.jar.asc)
  • javadoc jar (file name is project-name-javadoc.jar.asc)
  • sources jar (file name is project-name-sources.jar.asc)
  • pom file (file name is project-name.pom.asc, on this signature this post will mention later)

To sign archives is available via signing plugin.

build.gradle
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// adding 'signing' plugin
apply plugin: 'signing'
// summarize artifacts
artifacts {
    archives jar
    archives sourceJar
    archives javadocJar
}
// sign all artifacts
task signJars (type : Sign, dependsOn: [jar, javadocJar, sourceJar]) {
    sign configurations.archives
}
// call signJar task before publish task
task preparePublish(dependsOn: signJar)
// extract signature file and give them proper name
def getSignatureFiles = {
    def allFiles = project.tasks.signJars.signatureFiles.collect { it }
    def signedSources = allFiles.find { it.name.contains('-sources') }
    def signedJavadoc = allFiles.find { it.name.contains('-javadoc') }
    def signedJar = (allFiles - [signedSources, signedJavadoc])[0]
    return [
            [archive: signedSources, classifier: 'sources', extension: 'jar.asc'],
            [archive: signedJavadoc, classifier: 'javadoc', extension: 'jar.asc'],
            [archive: signedJar,     classifier: null,      extension: 'jar.asc']
    ]
}
publishing {
    publications {
        signatures (MavenPublication) {
            // give signature files to rtifact method
            getSignatureFiles().each {signature ->
                artifact (signature.archive) {
                    classifier = signature.classifier
                    extension  = signature.extension
                }
            }
        }
    }
}

Signing POM

Before running publish task, there are no POM file, so calling signing POM task will fail. To avoid this, whether calling POM task or not is defined dynamicly. And writing POM is available writeTo(File) method via XmlProviderContainer (i.e. on the Closure block of pom.withXml)

build.gradle
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
ext {
    pomFilePath = "${project.projectDir}/tmp/pom.xml"
    pomFile = file(pomFilePath)
}
configurations {
    pom
}
artifacts {
    archives jar
    archives sourceJar
    archives javadocJar
    if (pomFile.exists()) {
        pom pomFile
    }
}
task signPom(type: Sign) {
    sign configurations.pom
}
def getPomSignature = {
    return project.tasks.signPom.signatureFiles.collect{it}[0]
}
if (project.ext.pomFile.exists()) {
    task preparePublication (dependsOn : [signJars, signPom])
} else {
    task preparePublication (dependsOn : signJars)
}
publishing {
    publications {
        jar(MavenPublication) {
            // publishing main jars
            pom.withXml {
                // add required elements
                // here writing pom file
                if (!project.ext.pomFile.exists()) {
                    writeTo (project.ext.pomFile)
                }
            }
        }
        gpgJars(MavenPublication) {
            // publishing signature of jars
        }
        // dynamic publication definition
        // pom file does exist signature of pom file is published
        if (project.ext.pomFile.exists()) {
            gpgPom(MavenPublication) {
                artifact (getPomSignature()) {
                    classifier = null
                    extension  = 'pom.asc'
                }
            }
        }
    }
    repositories {
        maven {
            if (project.ext.pomFile.exists()) {
                url sonatypeUrl
                credentials {
                    username = sonatypeUsername
                    password = sonatypePassword
                }
            } else {
                url fileDirectory
            }
        }
    }
}

and execute gradle tasks as follows

build.gradle
1
2
$ gradle clean pP publish
$ gradle clean pP publish

You should execute gradle publish task twice.

  1. The first execution is generating pom file and publishing som artifacts to machine’s directory.
  2. The second execution is publishing pom signature to Sonatype OSS repository.

Please note…

publish task will execute publication tasks according to the alphabetiacl order of publishing task name. And each publication task will generate POM file. So please take care of publication name. The recomending name for publications is …

  • gpgJars – publish signatures of jar files.
  • gpgPom – publish signature of POM.
  • jar – publish all jars and POM.

Credential

You may know an account of Sonatype OSS is required to upload artifact into maven central repo. Here shows settings of sonatype account in maven-publish plugin.

build.gradle
1
2
3
4
5
6
7
8
9
publishing {
    repositories {
        url 'https://oss.sonatype.org/service/local/staging/deploy/maven2/'
        credentials {
            username = sonatypeUsername
            password = sonatypePassword
        }
    }
}

Conclusion

Taking these things in account, here is a perfect example script for publishing artifacts to maven central repo with maven-publish plugin.

build.gradle
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
['java', 'siging', 'maven-publish'].each {
    apply plguin: it
}
// project information
group = 'com.yourdomain'
version = '1.0'
// dependency management as you like
repositories {
    mavenCentral ()
}
dependencies {
    compile 'org.apache.commons:commons-lang3:3.1'
    testCompile 'junit:junit:4.11'
}
// javadoc.jar generation
task javadocJar (type: Jar, dependsOn: javadoc) { // (1)
    classifier = 'javadoc'
    from javadoc.destinationDir
}
// sources.jar generation
task sourceJar (type : Jar) {
    classifier = 'sources'
    from sourceSets.main.allSource
}
// pom file name
ext {
    pomFilePath = "${project.projectDir}/tmp/pom.xml"
    pomFile = file(pomFilePath)
}
// add configuration for pom signing
configurations {
    pom
}
// summarize artifacts
artifacts {
    archives jar
    archives sourceJar
    archives javadocJar
    if (pomFile.exists()) {
        pom pomFile
    }
}
// sign all artifacts
task signJars (type : Sign, dependsOn: [jar, javadocJar, sourceJar]) {
    sign configurations.archives
}
// sign pom
task signPom(type: Sign) {
    sign configurations.pom
}
// defining which tasks should be called
if (project.ext.pomFile.exists()) {
    task preparePublication (dependsOn : [signJars, signPom])
} else {
    task preparePublication (dependsOn : signJars)
}
// extract signatures and add classifier and extension to them
def getSignatureFiles = {
    def allFiles = project.tasks.signJars.signatureFiles.collect { it }
    def signedSources = allFiles.find { it.name.contains('-sources') }
    def signedJavadoc = allFiles.find { it.name.contains('-javadoc') }
    def signedJar = (allFiles - [signedSources, signedJavadoc])[0]
    return [
            [archive: signedSources, classifier: 'sources', extension: 'jar.asc'],
            [archive: signedJavadoc, classifier: 'javadoc', extension: 'jar.asc'],
            [archive: signedJar,     classifier: null,      extension: 'jar.asc']
    ]
}
// extract pom signature
def getPomSignature = {
    return project.tasks.signPom.signatureFiles.collect{it}[0]
}
publishing {
    publicaitons {
        gpgJars(MavenPublication) {
            getSignatureFiles().each {signature ->
                artifact (signature.archive) {
                    classifier = signature.classifier
                    extension  = signature.extension
                }
            }
        }
        if (project.ext.pomFile.exists()) {
            gpgPom(MavenPublication) {
                artifact (getPomSignature()) {
                    classifier = null
                    extension  = 'pom.asc'
                }
            }
        }
        jar(MavenPublication) {
            from components.java
            pom.withXml {
                asNode().children().last() + {
                    resolveStrategy = Closure.DELEGATE_FIRST
                    name 'project-name'
                    description 'description for project'
                    url projectUrl
                    scm {
                        url scmUrl
                        connection connectionUrl
                        developerConnection developerConnectionUrl
                    }
                    licenses {
                        license {
                            name 'The Apache Software License, Version 2.0'
                            url 'http://www.apache.org/license/LICENSE-2.0.txt'
                            distribution 'repo'
                        }
                    }
                    developers {
                        developer {
                            id 'your id or nick name'
                            name 'Your Name'
                            email 'your@mail.address'
                        }
                    }
                }
            }
        }
    }
    repositories {
            if (project.ext.pomFile.exists()) {
                url 'https://oss.sonatype.org/service/local/staging/deploy/maven2/'
                credentials {
                    username = sonatypeUsername
                    password = sonatypePassword
                }
            } else {
                url fileDirectory
            }
    }
}

and execute gradle tasks as follows

build.gradle
1
2
$ gradle clean pP publish
$ gradle clean pP publish

… Eh? maven plugin is easier than this way? You, right!

But this plugin will become more smart, I believe.