2012年6月29日金曜日

Java 7 の try-with-resources をさわってみるを、突っ込まれる的な

はい、Javaバイトコードが読めないみけです。



本題


昨日のこのタイトルとほぼ同名の記事を書いた後にさくらばさんに

アドバイスもらいました。



おお、そういえば、いろふさんブログにもあったような…

Throwable#getSuppressed() なるものがあって、最初に投げられた例外に

そのあとに投げられた例外が保存されているようです。


気を取り直して


昨日のコードを書きなおしてみました。

package org.mikeneck.j7;
import org.mikeneck.j7.resource.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Arrays;
import java.util.List;
/**
* Copyright 2012- @mike_neck
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
public class AutoCloseableSample {
Logger logger = LoggerFactory.getLogger(AutoCloseableSample.class);
public static void main (String[] args) {
AutoCloseableSample sample = new AutoCloseableSample();
sample.run();
}
public void run () {
for (When when : When.values()) {
logger.info("{} - [1] before open resource", when.formattedName());
try (Resource act = new Resource(when)) {
logger.info("{} - [2] before act something", when.formattedName());
act.something();
logger.info("{} - [3] after act something", when.formattedName());
} catch (OnConstructorException e) {
logger.info("{} - [4] exception on construction {}",
when.formattedName(), e.getMessage());
messagingSuppressedErrors(when, 5, e);
} catch (OnMethodException e) {
logger.info("{} - [6] exception on act something {}",
when.formattedName(), e.getMessage());
messagingSuppressedErrors(when, 7, e);
} catch (OnCloseException e) {
logger.info("{} - [8] exception on closing {}",
when.formattedName(), e.getMessage());
}
logger.info("{} - [9] finished", when.formattedName());
}
}
private void messagingSuppressedErrors (final When when,
final Integer messageNumber,
final Throwable t) {
List<Throwable> list = Arrays.asList(t.getSuppressed());
if (list.size() > 0) {
String errors = extractMessages(list);
logger.info("{} - [{}] suppressed exceptions {}",
new Object[]{when.formattedName(), messageNumber, errors});
}
}
private String extractMessages(List<Throwable> list) {
StringBuilder builder = new StringBuilder();
builder.append("[");
for (Throwable throwable : list) {
builder.append(throwable.getMessage());
}
builder.append("]");
return builder.toString();
}
}


catchの部分で他に例外がsuppressされていないか確認してみました。

で、実行結果がこれです。

AutoCloseableSample - OnConstructorOnly - [1] before open resource
Resource - OnConstructorOnly - [a] constructing
AutoCloseableSample - OnConstructorOnly - [4] exception on construction OnConstructorOnly
AutoCloseableSample - OnConstructorOnly - [9] finished
AutoCloseableSample - OnConstructorWithClose - [1] before open resource
Resource - OnConstructorWithClose - [a] constructing
AutoCloseableSample - OnConstructorWithClose - [4] exception on construction OnConstructorWithClose
AutoCloseableSample - OnConstructorWithClose - [9] finished
AutoCloseableSample - OnMethodOnly - [1] before open resource
Resource - OnMethodOnly - [a] constructing
AutoCloseableSample - OnMethodOnly - [2] before act something
Resource - OnMethodOnly - [b] acting
Resource - OnMethodOnly - [c] closing
AutoCloseableSample - OnMethodOnly - [6] exception on act something OnMethodOnly
AutoCloseableSample - OnMethodOnly - [9] finished
AutoCloseableSample - OnMethodWithClose - [1] before open resource
Resource - OnMethodWithClose - [a] constructing
AutoCloseableSample - OnMethodWithClose - [2] before act something
Resource - OnMethodWithClose - [b] acting
Resource - OnMethodWithClose - [c] closing
AutoCloseableSample - OnMethodWithClose - [6] exception on act something OnMethodWithClose
AutoCloseableSample - OnMethodWithClose - [7] suppressed exceptions [OnMethodWithClose]
AutoCloseableSample - OnMethodWithClose - [9] finished
AutoCloseableSample - OnCloseOnly - [1] before open resource
Resource - OnCloseOnly - [a] constructing
AutoCloseableSample - OnCloseOnly - [2] before act something
Resource - OnCloseOnly - [b] acting
AutoCloseableSample - OnCloseOnly - [3] after act something
Resource - OnCloseOnly - [c] closing
AutoCloseableSample - OnCloseOnly - [8] exception on closing OnCloseOnly
AutoCloseableSample - OnCloseOnly - [9] finished
AutoCloseableSample - NoException - [1] before open resource
Resource - NoException - [a] constructing
AutoCloseableSample - NoException - [2] before act something
Resource - NoException - [b] acting
AutoCloseableSample - NoException - [3] after act something
Resource - NoException - [c] closing
AutoCloseableSample - NoException - [9] finished
view raw console.log hosted with ❤ by GitHub


メソッド実行中とクローズ中に発生した例外について、

メソッド実行中の例外にクローズ中に発生した例外がsuppressされているのが確認できます。

実際、どうなってんの?


さて、さくらばさんからこんなアドバイスももらっています。



というわけで、デコンパイルしてみようとしたらJDってツールがダウンロード出来ない…(´・ω・`)

Jadの方をダウンロードしてデコンパイルしてみました。

/* Decompiled through IntelliJad */
// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packfields(3) packimports(3) splitstr(64) radix(10) lradix(10)
// Source File Name: AutoCloseableSample.java
package org.mikeneck.j7;
import java.util.*;
import org.mikeneck.j7.resource.OnCloseException;
import org.mikeneck.j7.resource.OnConstructorException;
import org.mikeneck.j7.resource.OnMethodException;
import org.mikeneck.j7.resource.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
// Referenced classes of package org.mikeneck.j7:
// When
public class AutoCloseableSample
{
public AutoCloseableSample()
{
logger = LoggerFactory.getLogger("org/mikeneck/j7/AutoCloseableSample");
}
public static void main(String args[])
{
AutoCloseableSample sample = new AutoCloseableSample();
sample.run();
}
public void run()
{
When arr$[];
int len$;
int i$;
arr$ = When.values();
len$ = arr$.length;
i$ = 0;
_L3:
if(i$ >= len$) goto _L2; else goto _L1
_L1:
When when;
when = arr$[i$];
logger.info("{} - [1] before open resource", when.formattedName());
Resource act;
Throwable throwable;
act = new Resource(when);
throwable = null;
try
{
logger.info("{} - [2] before act something", when.formattedName());
act.something();
logger.info("{} - [3] after act something", when.formattedName());
}
catch(Throwable throwable1)
{
throwable = throwable1;
throw throwable1;
}
if(act != null)
if(throwable != null)
try
{
act.close();
}
catch(Throwable x2)
{
throwable.addSuppressed(x2);
}
else
act.close();
break MISSING_BLOCK_LABEL_276;
Exception exception;
exception;
if(act != null)
if(throwable != null)
try
{
act.close();
}
catch(Throwable x2)
{
throwable.addSuppressed(x2);
}
else
act.close();
throw exception;
OnConstructorException e;
e;
logger.info("{} - [4] exception on construction {}", when.formattedName(), e.getMessage());
messagingSuppressedErrors(when, Integer.valueOf(5), e);
break MISSING_BLOCK_LABEL_276;
e;
logger.info("{} - [6] exception on act something {}", when.formattedName(), e.getMessage());
messagingSuppressedErrors(when, Integer.valueOf(7), e);
break MISSING_BLOCK_LABEL_276;
e;
logger.info("{} - [8] exception on closing {}", when.formattedName(), e.getMessage());
logger.info("{} - [9] finished", when.formattedName());
i$++;
goto _L3
_L2:
}
private void messagingSuppressedErrors(When when, Integer messageNumber, Throwable t)
{
List list = Arrays.asList(t.getSuppressed());
if(list.size() > 0)
{
String errors = extractMessages(list);
logger.info("{} - [{}] suppressed exceptions {}", new Object[] {
when.formattedName(), messageNumber, errors
});
}
}
private String extractMessages(List list)
{
StringBuilder builder = new StringBuilder();
builder.append("[");
Throwable throwable;
for(Iterator i$ = list.iterator(); i$.hasNext(); builder.append(throwable.getMessage()))
throwable = (Throwable)i$.next();
builder.append("]");
return builder.toString();
}
Logger logger;
}


ところどころ残念なことになってはいますが、まあ読めますね。

メソッド実行中とクローズ中の例外に例外が発生するパターンでは…

  • 52行目のtryからが本来のtryに該当するところで、55行目のact.something()で例外が発生。
  • 63行目と64行目の条件はtrueになるので、67行目のact.close()を実行して例外が発生。
  • 二度目に発生した例外は69行目のcatchで捕まって、71行目で最初に発生した例外にaddSuppressed(java.lang.Throwable)される。
  • 76行目以降の同じようなコードはここまで例外が発生しなかった場合のautoclose()処理ですかね。
  • 92行目以降のe;ってなってるのは本来のJavaコードのcatch部分で、各例外のハンドリングが実施される。

といった感じでしょうか。

なるほど、こういう風にコンパイル時にコードが展開されるんですね。

副作用


というわけで、昨日の記事を訂正したんですが、

どうやらバイトコードに興味を持ちました。

で、そういったあたりをTwitterで呟いたら



ツイッター怖っ!

JavaとJUnit雑感

こんちは。

みけです。

昨日はいろんなもののリリースというか発表というかありましたね。

2012年の半分も過ぎたわけで、成果発表という感じでしょうか。

アノテーション


僕はまあ、SIerにでっぷり浸かって、目先の仕事に追われていた時期が

長かったため、最近になってやっとプログラムに目覚めた感じの

残念な中年のおっさんなわけですが、

アノテーションとかAPT、PAP-APIとかを知ったあたりで、

結構衝撃があったわけですね。

Javaでメタ情報を扱えると。


JUnit


で、その衝撃を持ったまま、

Androidテスト部の勉強会に参加して、

「テストの自動化?そんなの普通だよ」という人達の

お話を聞いたものですから、

なんとなくの勘というか直感だったのですが、

「アノテーションでJUnit回せるんじゃね?」という

妙な自信みたいなものが生まれてきました。



人間の直感というのは時に熟考よりも的確な場合があるもので、

昨日(2012/06/28)のtry-with-resourcesの記事を書いていた時に、

テスト条件をenumで回す実装を書いてて、

「アノテーションとenumをPAP-APIから使えば、

コンパイル時にテストできそうだな」という確信に似たものを

なんか持ったようです。


どうなんだろう、ちょっとやってみたい気もするなぁ…

2012年6月28日木曜日

Groovy2.0キタ━(゚∀゚)━!

こんにちわ。

みけです。

とりま


Groovy2.0が来ました。

さて、新機能として、static type checkingが追加されたようです。

というわけで、早速試してみました。


左がGroovy2.0で右がGroovy1.8.6です。

これまでは実行時まで型の安全性を保証できなかったのですが…

(とはいえ、IDEが頑張ってくれてた)

TypeCheckedをつけるだけで、あら不思議、

コンパイル時に型チェックしてくれます。


結局Groovy型安全じゃないじゃんという批判には耳を貸しません( ー`дー´)キリッ


ちなみにJava7のinvoke dynamicにも対応して実行時性能が向上したそうです。

その他いろいろについてはinfoQの記事を参照。

Java 7 の try-with-resources をさわってみる

こんにちわ。みけです。

Java6のEOLが今年(2012年)の11月に迫っています。

なので、Java6を使っている人はJava7に切り替え始めましょう。

Javaの最新7の5分の1のバージョンを使っている人(つまりJava1.4を使っている人)はとっととアップデートして下さい。

Java1.4のEOLは2008年10月30日です。

Sunと契約している場合を除いて、契約していない人たちは泣き言を言わずアップデートしましょう。


…以前も同じ事書いた記憶が…

本題


昨日(2012/06/27) java-jaの『Log.debug("nice catch!")』に行って来ました。

開催者のよしおりさん、やましろさん、いつもご苦労さまです。

まあ、ツイッターとかまとめられているので、当日の様子については

そっちを御覧ください。しんやさん本当ご苦労さまです。

で、例外ハンドリングということで、Java7の例外ハンドリング、

try-with-resourcesが話題になったので、少し調べてみました。


ggrks


まあ、調べるったって、ggrだけなんですが、

たくさんエントリーありますね(白目。

なので、詳しい話はそっちを見て下さい。


そうは言っても


僕は自分の指を動かしたことしか理解できない単純な構造をしているので、

他のブログのエントリーと同様にコードを書いてみました。

package org.mikeneck.j7;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
/**
* Copyright 2012- @mike_neck
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
public class AutoCloseableTest {
private static Logger logger = LoggerFactory.getLogger(AutoCloseableTest.class);
@Test
public void autoCloseable () {
List<When> list = new ArrayList<>();
for (When when : When.values()) {
logger.info("{} 1. before try", when);
try (Resource resource = new Resource(when)) {
logger.info("{} 2 before do something", when);
resource.doSomething();
logger.info("{} 3 after do something", when);
} catch (ResourceException e) {
logger.info("{} 4 catch exception / e: {}", when, e);
list.add(e.getWhen());
} catch (ClosingException e) {
logger.info("{} 5 closing exception / e: {}", when, e);
list.add(e.getWhen());
} finally {
logger.info("{} 6 finally", when);
}
}
assertThat(list.size(), is(5));
}
static class Resource implements AutoCloseable {
private When when;
Resource (final When when) throws ResourceException {
logger.info("{} i constructor", when);
this.when = when;
if (when.onConstructor()) {
throw new ResourceException(when);
}
}
public void doSomething () throws ResourceException {
logger.info("{} ii do something", when);
if (when.onMethod()) {
throw new ResourceException(when);
}
}
@Override
public void close () throws ClosingException {
logger.info("{} iii close", when);
if (when.onClose()) {
logger.info("{} iv on close", when);
throw new ClosingException(when);
}
}
}
static class ResourceException extends Exception {
private When when;
ResourceException (When when) {
super(when.name());
this.when = when;
}
public When getWhen() {
return when;
}
}
static class ClosingException extends Exception {
private When when;
ClosingException (When when) {
super(when.name());
this.when = when;
}
public When getWhen() {
return when;
}
}
}


まあ、テストの形をとっているのが他のブログと少しだけ違うかな。

で、テスト条件は次のWhen.javaというenumです。

package org.mikeneck.j7;
/**
* Copyright 2012- @mike_neck
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
public enum When {
OnConstructorOnly {
@Override boolean onConstructor() {return true;}
@Override boolean onMethod() {return false;}
@Override boolean onClose() {return false;}
},
OnConstructorWithClose {
@Override boolean onConstructor() {return true;}
@Override boolean onMethod() {return false;}
@Override boolean onClose() {return true;}
},
OnMethodOnly {
@Override boolean onConstructor() {return false;}
@Override boolean onMethod() {return true;}
@Override boolean onClose() {return false;}
},
OnMethodWithClose {
@Override boolean onConstructor() {return false;}
@Override boolean onMethod() {return true;}
@Override boolean onClose() {return true;}
},
OnCloseOnly {
@Override boolean onConstructor() {return false;}
@Override boolean onMethod() {return false;}
@Override boolean onClose() {return true;}
},
NoException {
@Override boolean onConstructor() {return false;}
@Override boolean onMethod() {return false;}
@Override boolean onClose() {return false;}
};
abstract boolean onConstructor();
abstract boolean onMethod();
abstract boolean onClose();
}
view raw When.java hosted with ❤ by GitHub


enumにメソッドが実装できるのは周知の話で、

今回はenumの各値に条件を返すような実装を与えました。

  • onConstructorメソッドはコンストラクターで例外が発生する・しないを表します。
  • onMethodメソッドはメソッドの途中で例外が発生する・しないを表します。
  • onCloseメソッドはjava.lang.AutoCloseableで定義されているvoid close()メソッドで例外が発生する・しないを表します。

まあ、コード見ればわかりますが、

テスト条件を日本語でメモっとくとこんな感じ。

  • OnConstructorOnly
    • コンストラクター : 発生する
    • メソッド : 発生しない
    • クローズ : 発生しない
  • OnConstructorWithClose
    • コンストラクター : 発生する
    • メソッド : 発生しない
    • クローズ : 発生する
  • OnMethodOnly
    • コンストラクター : 発生しない
    • メソッド : 発生する
    • クローズ : 発生しない
  • OnMethodWithClose
    • コンストラクター : 発生しない
    • メソッド : 発生する
    • クローズ : 発生する
  • OnCloseOnly
    • コンストラクター : 発生しない
    • メソッド : 発生しない
    • クローズ : 発生する
  • NoException
    • コンストラクター : 発生しない
    • メソッド : 発生しない
    • クローズ : 発生しない

そうそう、これやってて気づいたんですが、

enum使えば、デシジョンテーブル系のテストをすんなり記述できますね。

まあ、enumをテスト対象に挟みこむにはインターフェースを使わないと難しいですが…


テスト実行


テスト実行しました。

コンソール出力は次のとおりです。

06/28 15:00:31 883 [ INFO] [main ] AutoCloseableTest - OnConstructorOnly 1. before try
06/28 15:00:31 884 [ INFO] [main ] AutoCloseableTest - OnConstructorOnly i constructor
06/28 15:00:31 885 [ INFO] [main ] AutoCloseableTest - OnConstructorOnly 4 catch exception / e: org.mikeneck.j7.AutoCloseableTest$ResourceException: OnConstructorOnly
06/28 15:00:31 886 [ INFO] [main ] AutoCloseableTest - OnConstructorOnly 6 finally
06/28 15:00:31 887 [ INFO] [main ] AutoCloseableTest - OnConstructorWithClose 1. before try
06/28 15:00:31 887 [ INFO] [main ] AutoCloseableTest - OnConstructorWithClose i constructor
06/28 15:00:31 888 [ INFO] [main ] AutoCloseableTest - OnConstructorWithClose 4 catch exception / e: org.mikeneck.j7.AutoCloseableTest$ResourceException: OnConstructorWithClose
06/28 15:00:31 889 [ INFO] [main ] AutoCloseableTest - OnConstructorWithClose 6 finally
06/28 15:00:31 889 [ INFO] [main ] AutoCloseableTest - OnMethodOnly 1. before try
06/28 15:00:31 890 [ INFO] [main ] AutoCloseableTest - OnMethodOnly i constructor
06/28 15:00:31 890 [ INFO] [main ] AutoCloseableTest - OnMethodOnly 2 before do something
06/28 15:00:31 891 [ INFO] [main ] AutoCloseableTest - OnMethodOnly ii do something
06/28 15:00:31 891 [ INFO] [main ] AutoCloseableTest - OnMethodOnly iii close
06/28 15:00:31 892 [ INFO] [main ] AutoCloseableTest - OnMethodOnly 4 catch exception / e: org.mikeneck.j7.AutoCloseableTest$ResourceException: OnMethodOnly
06/28 15:00:31 893 [ INFO] [main ] AutoCloseableTest - OnMethodOnly 6 finally
06/28 15:00:31 893 [ INFO] [main ] AutoCloseableTest - OnMethodWithClose 1. before try
06/28 15:00:31 894 [ INFO] [main ] AutoCloseableTest - OnMethodWithClose i constructor
06/28 15:00:31 894 [ INFO] [main ] AutoCloseableTest - OnMethodWithClose 2 before do something
06/28 15:00:31 895 [ INFO] [main ] AutoCloseableTest - OnMethodWithClose ii do something
06/28 15:00:31 895 [ INFO] [main ] AutoCloseableTest - OnMethodWithClose iii close
06/28 15:00:31 896 [ INFO] [main ] AutoCloseableTest - OnMethodWithClose iv on close
06/28 15:00:31 897 [ INFO] [main ] AutoCloseableTest - OnMethodWithClose 4 catch exception / e: org.mikeneck.j7.AutoCloseableTest$ResourceException: OnMethodWithClose
06/28 15:00:31 898 [ INFO] [main ] AutoCloseableTest - OnMethodWithClose 6 finally
06/28 15:00:31 898 [ INFO] [main ] AutoCloseableTest - OnCloseOnly 1. before try
06/28 15:00:31 899 [ INFO] [main ] AutoCloseableTest - OnCloseOnly i constructor
06/28 15:00:31 900 [ INFO] [main ] AutoCloseableTest - OnCloseOnly 2 before do something
06/28 15:00:31 900 [ INFO] [main ] AutoCloseableTest - OnCloseOnly ii do something
06/28 15:00:31 900 [ INFO] [main ] AutoCloseableTest - OnCloseOnly 3 after do something
06/28 15:00:31 901 [ INFO] [main ] AutoCloseableTest - OnCloseOnly iii close
06/28 15:00:31 901 [ INFO] [main ] AutoCloseableTest - OnCloseOnly iv on close
06/28 15:00:31 902 [ INFO] [main ] AutoCloseableTest - OnCloseOnly 5 closing exception / e: org.mikeneck.j7.AutoCloseableTest$ClosingException: OnCloseOnly
06/28 15:00:31 903 [ INFO] [main ] AutoCloseableTest - OnCloseOnly 6 finally
06/28 15:00:31 903 [ INFO] [main ] AutoCloseableTest - NoException 1. before try
06/28 15:00:31 904 [ INFO] [main ] AutoCloseableTest - NoException i constructor
06/28 15:00:31 904 [ INFO] [main ] AutoCloseableTest - NoException 2 before do something
06/28 15:00:31 904 [ INFO] [main ] AutoCloseableTest - NoException ii do something
06/28 15:00:31 905 [ INFO] [main ] AutoCloseableTest - NoException 3 after do something
06/28 15:00:31 905 [ INFO] [main ] AutoCloseableTest - NoException iii close
06/28 15:00:31 905 [ INFO] [main ] AutoCloseableTest - NoException 6 finally
view raw console.log hosted with ❤ by GitHub


これをみててわかるのはclose()メソッド中に例外が発生した場合、

  • 例外発生後に例外が発生した場合は握りつぶす!
  • 正常に終了しているときに例外が発生した場合は例外を通知する

という動きをしていますね。

まあ、リソース処理系の例外の処理ってだいたいそうですけどね…


ロギング


ちなみにSlf4jでログを出力していますが、

ログ実装はLog4jです(´・ω・`)

なお、ログ実装については



ってことで、Markerについても覚えておきたいですね。

ちなみに日本語で最も正しいMarkerの使い方は

たいちさんの『設計と実装の狭間』でだそうです。



2012年6月27日水曜日

JavaFXのApplication Threadと戯れる-その2

ニャル子さんが終わったので、アイコンを元に戻しました。

みけです。

スレッド周り


Fx-Js-JUnitの話で少しだけ触れましたが、

JavaFXアプリケーションはスレッド周りが大変です。

単品のJavaFXアプリケーションを作る分には、

それほど問題はありませんが、

JUnitと合体させたものを作ろうとすると、

スレッドに関する知識がないと

マジで難しくなります。

死ねます。

死なないで下さい。


プログラムが処理されていく順番をしっかり覚える


というわけで、マルチスレッドなプログラムの処理順を

しっかり抑えておくことが大切です。

というわけで、アプリケーションの起動から終了に至るまでの

順番をログに出力するサンプルコードを書いてみました。

package org.mikeneck.jfx
import java.util.concurrent.Executors
import java.util.concurrent.atomic.AtomicInteger
import javafx.application.Application
import javafx.application.Platform
import javafx.stage.Stage
import org.junit.Before
import org.junit.Test
import groovy.util.logging.Slf4j
/**
* Copyright 2012- @mike_neck
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@Slf4j
class FxThreadTest {
static App app = null
static boolean finished = false
static final AtomicInteger count = new AtomicInteger()
@Before
void setApp () {
app = null
finished = false
count.set(0)
}
@Test
void applicationThreadTest () {
def service = Executors.newFixedThreadPool(2)
log.info "${count.andIncrement} テストスタート"
service.execute(new Runnable(){
@Override void run () {
log.info "${count.andIncrement} アプリ起動する"
Application.launch(App)
log.info "${count.andIncrement} アプリ起動した"
}
})
while (app == null) {
sleep 100L
}
assert app.message == 'hello'
log.info "${count.andIncrement} アプリのインスタンス取れた"
service.execute(new Runnable(){
@Override void run () {
log.info "${count.andIncrement} アプリ終了する"
Platform.exit()
log.info "${count.andIncrement} アプリ終了した"
}
})
while (app != null) {
sleep 100L
}
assert finished == true
log.info "${count.andIncrement} テスト終わり"
}
static class App extends Application {
String message
@Override
void start(Stage stage) {
log.info "${count.andIncrement} アプリ始まるよ!"
message = 'hello'
app = this
}
@Override
void stop() {
super.stop()
log.info "${count.andIncrement} アプリ終わっちゃうの?"
app = null
finished = true
}
}
}


これは単純にアプリケーションを起動して、

終了するだけのコードです。


クイズ


さて、ここで問題です。

Application.launch(App)の後にある

アプリ起動したというログが出力されるのは何番目でしょうか?


宣伝


7月2日に@skrbさん主催の

『第 7 回 JavaFX 勉強会 ツール特集』にてLTやります。

ユーストもあります。

ぜひお楽しみに!


答え


実行した結果を以下に示します。

06/27 13:58:45 582 [ INFO] [main ] FxThreadTest - 0 テストスタート
06/27 13:58:45 614 [ INFO] [pool-1-thread-1 ] FxThreadTest - 1 アプリ起動する
06/27 13:58:45 959 [ INFO] [JavaFX Application Thread] FxThreadTest - 2 アプリ始まるよ!
06/27 13:58:46 021 [ INFO] [main ] FxThreadTest - 3 アプリのインスタンス取れた
06/27 13:58:46 026 [ INFO] [pool-1-thread-2 ] FxThreadTest - 4 アプリ終了する
06/27 13:58:46 028 [ INFO] [pool-1-thread-2 ] FxThreadTest - 5 アプリ終了した
06/27 13:58:46 029 [ INFO] [JavaFX Application Thread] FxThreadTest - 6 アプリ終わっちゃうの?
06/27 13:58:46 034 [ INFO] [pool-1-thread-1 ] FxThreadTest - 7 アプリ起動した
06/27 13:58:46 127 [ INFO] [main ] FxThreadTest - 8 テスト終わり
view raw console.log hosted with ❤ by GitHub


アプリ起動したは、アプリ終わっちゃうの?の後に着ていますね。

要するにApplication.launch(App)の後はスレッドは残ったまま、

アプリケーションの終了を待機してしまいます。

したがって、「アプリを起動して、それから何かの操作をアプリに対して実行して」

というシナリオでテストを書く場合には、

必ず別スレッドでアプリケーションを起動する

ようにしましょう。

2012年6月18日月曜日

JavaFXのApplication Threadと戯れる

みなさんJavaFXで遊んでいますか?

みけです。

ふと気になったこと


Fx-Js-JUnitはいい感じに出来上がりつつ有るのですが、

そういえば、複数のJavaFXアプリケーションを起動できるのか

気になったので、ちょっとやってみました。


package org.mikeneck.jfx.multiapp;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @author : mike
* @since : 12/06/13
*/
public class MultiThreadApplicationExecutor {
private final int threads = 2;
ExecutorService service = Executors.newFixedThreadPool(threads);
public static void main (String[] args) {
MultiThreadApplicationExecutor main = new MultiThreadApplicationExecutor();
main.run ();
}
public void run () {
List<Runner> runners = new ArrayList<>(2);
for (int i = 0; i < threads; i++) {
runners.add(new Runner(i));
}
for (Runner runner : runners) {
try {
Thread.sleep(4000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
service.execute(runner);
}
}
}


複数のスレッドからJavaFXアプリケーションを起動することで同時実行できるか確認します。


package org.mikeneck.jfx.multiapp;
import javafx.application.Application;
import javafx.application.Platform;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Date;
/**
* @author : mike
* @since : 12/06/13
*/
public class Runner implements Runnable {
private static final Logger log = LoggerFactory.getLogger(Runner.class);
private int id;
public Runner (int id) {
this.id = id;
}
public void run() {
try {
log.info("application {} is starting at {}", this.id, new Date());
@SuppressWarnings("unchecked")
Class<? extends Application> fxApplication =
(Class<? extends Application>) Class.forName(
"org.mikeneck.jfx.multiapp.FxApplication");
Application.launch(fxApplication, getApplicationId());
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
log.info("sleep failed {}", e);
}
stop();
} catch (ClassNotFoundException e) {
log.info("class not found", e);
}
}
public void stop () {
log.info("application {} is stopping at {}", this.id, new Date());
Platform.runLater(new Runnable() {
@Override
public void run() {
Platform.exit();
}
});
}
public String getApplicationId () {
return "application : " + id;
}
}
view raw Runner.java hosted with ❤ by GitHub


JavaFXアプリケーションはGroovyで書いているので、Class#forName(java.lang.String)

使っています。

JavaFXアプリケーションはこんな感じです。


package org.mikeneck.jfx.multiapp
import javafx.application.Application
import javafx.stage.Stage
import groovy.util.logging.Slf4j
/**
*
* @author : mike
* @since : 12/06/14
*/
@Slf4j
class FxApplication extends Application {
static Map<String, FxApplication> map = [:]
@Override
void start(Stage stage) {
def id = getParameters().raw[0]
org.mikeneck.jfx.multiapp.FxApplication.log.info id
map << [(id) : this]
}
}



では、おもむろに実行します。

2012/06/18 07:14:42 [ INFO] Runner pool-1-thread-1 - application 0 is starting at Mon Jun 18 07:14:42 JST 2012
2012/06/18 07:14:42 [ INFO] FxApplication JavaFX Application Thread - application : 0
2012/06/18 07:14:46 [ INFO] Runner pool-1-thread-2 - application 1 is starting at Mon Jun 18 07:14:46 JST 2012
Exception in thread "pool-1-thread-2" java.lang.IllegalStateException: Application launch must not be called more than once
at com.sun.javafx.application.LauncherImpl.launchApplication(LauncherImpl.java:94)
at com.sun.javafx.application.LauncherImpl.launchApplication(LauncherImpl.java:75)
at javafx.application.Application.launch(Application.java:144)
at org.mikeneck.jfx.multiapp.Runner.run(Runner.java:31)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1110)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:603)
at java.lang.Thread.run(Thread.java:722)
Process finished with exit code 1
view raw console.txt hosted with ❤ by GitHub



java.lang.IllegalStateExceptionが出てますね。

アプリケーションの起動は一回までと怒られています。


(´・ω・`)

JavaFXアプリケーションの同時実行はできないようです。


じゃあシングルスレッドならどうなの?


マルチスレッドでJavaFXアプリケーションを起動できないなら、

シングルスレッドで何度も起動できるのか?

これも試してみました。


package org.mikeneck.jfx.order;
import javafx.application.Application;
import javafx.application.Platform;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
/**
* @author : mike
* @since : 12/06/16
*/
public class OrderedAppExecutor {
private static Logger logger = LoggerFactory.getLogger(OrderedAppExecutor.class);
private ExecutorService service = Executors.newFixedThreadPool(1);
private List<String> parameters = Arrays.asList("one", "two", "three", "four");
public static void main (String[] arg) throws InterruptedException {
OrderedAppExecutor application = new OrderedAppExecutor();
application.run();
logger.info("application finished");
System.exit(0);
}
public void run () throws InterruptedException {
for (final String parameter : parameters) {
service.execute(new Runnable() {
@Override
public void run() {
logger.info(parameter);
try {
Application.launch(OrderedApp.class, parameter);
} catch (IllegalStateException e) {
OrderedApp.fail();
logger.error("IllegalStateException", e);
}
}
});
while (OrderedApp.get() == null) {
Thread.sleep(200L);
if (OrderedApp.isFailed()) {
break;
}
logger.info("Synchronizing {}", parameter);
}
if (OrderedApp.get() != null) {
logger.info("application started {}", OrderedApp.get());
Thread.sleep(3000L);
final BlockingQueue<String> queue = new LinkedBlockingQueue<>();
Platform.runLater(new Runnable() {
@Override
public void run() {
Platform.exit();
try {
logger.info("application is going to stop {}", parameter);
queue.put(parameter);
} catch (InterruptedException e) {
logger.error("error while stopping application {} : {}", parameter, e);
}
}
});
logger.info("application stopped {}", queue.take());
Thread.sleep(3000L);
} else if (OrderedApp.isFailed()) {
logger.info("application failed {}", parameter);
}
OrderedApp.normalize();
}
}
}


単純にApplication#launch(java.lang.Class)Platform#exit()を繰り返す

コードです。

例のごとく、JavaFXのアプリケーション本体はGroovyで記述しています。


package org.mikeneck.jfx.order
import groovy.util.logging.Slf4j
import javafx.application.Application
import javafx.stage.Stage
/**
*
* @author : mike
* @since : 12/06/16
*/
@Slf4j
class OrderedApp extends Application {
static private String parameter
static private boolean failToLaunch = false
@Override
void start(Stage stage) {
log.info 'Parameter : {}', parameters.raw
parameter = parameters.raw[0]
}
@Override
void stop() {
parameter = null
super.stop()
}
static void fail () {
failToLaunch = true
}
static boolean isFailed () {
failToLaunch
}
static void normalize () {
failToLaunch = false
}
static String get () {
parameter
}
}



では起動してみましょう。


2012/06/18 07:23:47 [ INFO] OrderedAppExecutor pool-1-thread-1 - one
2012/06/18 07:23:47 [ INFO] OrderedApp JavaFX Application Thread - Parameter : [one]
2012/06/18 07:23:47 [ INFO] OrderedAppExecutor main - Synchronizing one
2012/06/18 07:23:47 [ INFO] OrderedAppExecutor main - application started one
2012/06/18 07:23:50 [ INFO] OrderedAppExecutor JavaFX Application Thread - application is going to stop one
2012/06/18 07:23:50 [ INFO] OrderedAppExecutor main - application stopped one
2012/06/18 07:23:53 [ INFO] OrderedAppExecutor pool-1-thread-1 - two
2012/06/18 07:23:53 [ERROR] OrderedAppExecutor pool-1-thread-1 - IllegalStateException
java.lang.IllegalStateException: Application launch must not be called more than once
at com.sun.javafx.application.LauncherImpl.launchApplication(LauncherImpl.java:94)
at com.sun.javafx.application.LauncherImpl.launchApplication(LauncherImpl.java:75)
at javafx.application.Application.launch(Application.java:144)
at org.mikeneck.jfx.order.OrderedAppExecutor$1.run(OrderedAppExecutor.java:41)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1110)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:603)
at java.lang.Thread.run(Thread.java:722)
2012/06/18 07:23:53 [ INFO] OrderedAppExecutor main - application failed two
2012/06/18 07:23:53 [ INFO] OrderedAppExecutor pool-1-thread-1 - three
2012/06/18 07:23:53 [ERROR] OrderedAppExecutor pool-1-thread-1 - IllegalStateException
java.lang.IllegalStateException: Application launch must not be called more than once
at com.sun.javafx.application.LauncherImpl.launchApplication(LauncherImpl.java:94)
at com.sun.javafx.application.LauncherImpl.launchApplication(LauncherImpl.java:75)
at javafx.application.Application.launch(Application.java:144)
at org.mikeneck.jfx.order.OrderedAppExecutor$1.run(OrderedAppExecutor.java:41)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1110)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:603)
at java.lang.Thread.run(Thread.java:722)
2012/06/18 07:23:54 [ INFO] OrderedAppExecutor main - application failed three
2012/06/18 07:23:54 [ INFO] OrderedAppExecutor pool-1-thread-1 - four
2012/06/18 07:23:54 [ERROR] OrderedAppExecutor pool-1-thread-1 - IllegalStateException
java.lang.IllegalStateException: Application launch must not be called more than once
at com.sun.javafx.application.LauncherImpl.launchApplication(LauncherImpl.java:94)
at com.sun.javafx.application.LauncherImpl.launchApplication(LauncherImpl.java:75)
at javafx.application.Application.launch(Application.java:144)
at org.mikeneck.jfx.order.OrderedAppExecutor$1.run(OrderedAppExecutor.java:41)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1110)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:603)
at java.lang.Thread.run(Thread.java:722)
2012/06/18 07:23:54 [ INFO] OrderedAppExecutor main - application failed four
2012/06/18 07:23:54 [ INFO] OrderedAppExecutor main - application finished
Process finished with exit code 0
view raw console.txt hosted with ❤ by GitHub



一回目の起動時はちゃんと起動して終了まで行っていますね。

ただ二回目以降はすべて落ちているようです。


結論


JavaFXアプリケーションは二度以上起動することはできぬ!

2012年6月15日金曜日

Java NIO2 をいじろう - その2


続きを書くのが面倒くさくなっていました。

みけです。

今回もjava.nio.file.Pathのメソッドを見ていきたいと思います。

iterator()

そのパスに含まれるパスを返してくれるのかとおもいきや、

トップパスからのパスをiteratorにして返してくれます。

かつ、ちょっと面白いことが発生します。

groovy:000> home = System.env['HOMEPATH']
===> \Users\mike
groovy:000> dir = new File(home)
===> \Users\mike
groovy:000> path = dir.toPath()
===> \Users\mike
groovy:000> path.iterator().each { println "$it -> ${it.toAbsolutePath()}" }
Users -> C:\Users\mike\Users
mike -> C:\Users\mike\mike
===> sun.nio.fs.AbstractPath$1@44eeef95

親パスになっていると思ったら、

子パスになっている(´・ω・`)

このメソッドを使う場合は気をつけないといけませんね。


relativize(java.nio.filePath)

これは結構便利だと思いました。

あるパスから別のパスへ相対パス化してくれます。

groovy:000> ghome = System.env['GROOVY_HOME']
===> C:\Groovy\1.8.6
groovy:000> jhome = System.env['JAVA_HOME']
===> C:\Program Files\Java\jdk1.7.0_02
groovy:000> gPath = new File(ghome).toPath()
===> C:\Groovy\1.8.6
groovy:000> jPath = new File(jhome).toPath()
===> C:\Program Files\Java\jdk1.7.0_02
groovy:000> jPath.relativize(gPath)
===> ..\..\..\Groovy\1.8.6


register(WatchService, WatchEvent.Kind<?>)

一番面白そうな感じのするメソッドなのですが、

調べ上げられていません。

また、いずれの機会にか…

己を知る ( Know yourself )

あれ、なんかBloggerの入力ページが変わっちゃいましたね。

みけです。

本題


己を知るという大層なタイトルにしましたが、

別にどうってことのないこと書いています。

「オレはこういうことできるんだよ~」とか、

「オレはこういう人だ!」とか

そういう自己暗示的というか意識的なことではありません。

もっと身体に即した部分のことです。


例えば


僕は比較的早起きな傾向があります。

季節にもよりますが、朝4時には目が覚めています。

これは無意識的に目が覚めてしまうので、

どうしようもないです。

さて、こうなると、睡眠時間はちゃんと確保したいものです。

なるべく10時前には寝るように心がけています。


他にも挙げてみよう


他にどんなのがあるでしょうか?

  • ある程度作業の見通しがたったら、やる気が無くなるというか飽きる
  • 朝の6時半頃に二度寝したくなる。
  • 昼1時から3時までとにかく眠い。

僕は多分根が真面目すぎるので、最初の状況になった時に、

後になってから(ここが大事)、「なんで最後までやらないかな?」と

過度に自分を責めてしまいます。

その結果、自分で自分を傷つけてしまいます。


二つ目の点は結構一日の生活そのものに影響があって、

ここで寝てしまうと一日のやる気が無くなってしまいます。

ただ、これにはちょっとしたヒントというか、癖があって、

二週間という周期をもって、この状態に対する評価が変わります。

悪い時は、寝てしまったら、それを取り返そうと無駄に意識だけが空回りして、

結局何も出来ません。

今週は悪い方の週です。

良い時は、寝てしまっても、まあできることだけやろうと、

のんびり構えて、できることを少しだけやります。

先週はこんな感じでした。

悪い方の週だと、「今日一日何もやってないじゃん」と

また自分を過度に責めてしまいます。


昼に眠くなる現象


これは皆さん良くあることだと思います。

ガムを噛む、散歩する、コーヒーを飲むなど対策がありますね。

僕はすべて試してみた結果、全部だめだったので、

諦めて寝ることにしています。

ちゃんと昼の2時間の睡眠を取ることで、

3時から夕方6時までの生産性を向上させるということに

しています。

実際に「よし寝るぞ!」と意気込んで寝ると、

3時から6時までの3時間、

この何かをひとつやりあげるのに十分な時間の3時間が

非常に有意義になります。


結論


というわけで、皆さんも自分のことをよく知ってみるように心がけてみてはいかがでしょうか?



2012年6月12日火曜日

Fx-Js-JUnitの改良1



誰か、Retina MBP僕に下さい。

みけです。

今のまずい点


まず、まったくもっていただけないのが、Fx-Js-JUnitがエンベデッドのサーバーしか想定していないところ。

昨日のテストコードで@ClassRuleアノテーションを付与した箇所を見てみましょう。

    @ClassRule
    static public UseFxWebView fxWebView = UseFxWebView
                                                .defaultServer()
                                                .identifiedBy('JsJUnitTest')
                                                .get()

このUseFxWebViewというクラスが実はエンベデッドサーバーに依存しているのです。

package org.mikeneck.jsjunit
import org.junit.rules.TestRule
import org.junit.runners.model.Statement
import org.junit.runner.Description
/**
*/
class UseFxWebView implements TestRule {
String hostName
int portNumber
String identifier
String webRoot
IntegrationTester tester
static Settings defaultServer () {
new Settings(hostName: Settings.DEFAULT_SERVER, portNumber: Settings.DEFAULT_PORT)
}
static Settings port (int portNumber) {
new Settings(hostName: Settings.DEFAULT_SERVER, portNumber: portNumber)
}
@Override
Statement apply(Statement base, Description description) {
tester = new IntegrationTester(
hostName: hostName,
portNumber: portNumber,
identifier: identifier,
webRoot: webRoot,
base: base,
description: description)
return tester
}
JsJUnit getJsTester () {
tester.getJsJUnit()
}
}


package org.mikeneck.jsjunit
/**
*/
class Settings {
public static final String DEFAULT_SERVER = 'localhost'
public static final int DEFAULT_PORT = 3000
String hostName
int portNumber
String identifier
Settings identifiedBy (String id) {
this.identifier = id
this
}
UseFxWebView get () throws IllegalAccessException {
if (canBeInitialized()) {
new UseFxWebView(hostName: hostName, portNumber: portNumber, identifier: identifier)
} else {
throw new IllegalAccessException('Settings is accessed illegally!')
}
}
UseFxWebView customRoot (String root) throws IllegalAccessException {
if (canBeInitialized()) {
new UseFxWebView(hostName: hostName, portNumber: portNumber, identifier: identifier, webRoot: root)
} else {
throw new IllegalAccessException('Settings is accessed illegally!')
}
}
private boolean canBeInitialized () {
return hostName != null && hostName.size() > 0 &&
portNumber != null && portNumber != 0 &&
identifier != null && identifier.size() > 0
}
}
view raw Settings.groovy hosted with ❤ by GitHub


ここはもうすこしちゃんとビルダーの設計をしてFx-Js-JUnitのコア(JavaFXでJavascriptのコードをJavaから呼び出す)部分とFx-Js-JUnitのエンベデッドサーバー(自プロジェクトのJavascriptを読み込めるようにする)を分離したいと思っています。

2012年6月11日月曜日

JavaFX + JUnit で javascriptのunit testできるようにしてやるんで、これからハマっていってやんよ - 4

前回のポストから約2ヶ月半、やっとできましたよFxJsJUnit。

みけです。


ひらめき


JavaFX2.0の発表を聞いた時にWebViewがwebkitを搭載するということで、

すぐにjavascriptのテストをJavaで書けるようになると閃いて、

今年の3月くらいにとりかかりました。

桜庭さんからJavaからJavascriptを呼び出すにはjavafx.scene.web.WebEngine#executeScript(java.lang.String)を叩けばよいと聞いていたので、

実際、初回に叩いてみたわけですが

JavaFXのコンポーネントはJavaFXスレッドで立ち上げないとダメということで、挫折しました。


成功?!


その後、桜庭さんのブログエントリーで色々とアドバイスを頂いたりしました。


それを参考にしてコードを書いてみたら、テスト1つは通ったのですが、複数回のテストが実行できないという残念な結果に終わりました


スレッド、スレッド、スレッド


もともと業務SEさんで、スレッドとか興味なかったのでスレッドの制御に悩みました。

3月末頃にはコードがグダグダになってきていて、どうしようもなくなっていたようです。

この頃は何に悩んでいたかというと、

  • テストを起動→Webサーバー起動→JavaFXアプリケーションを起動という順番で実行
  • JavaFXアプリケーションが起動完了したところで、何も動かなくなる

といった状態でどのスレッドで何がどうなっているかが全く?になっていました。

JUnitとWebサーバーとJavaFXアプリケーションとWebViewと4つのスレッドの同期化を図りつつ実行していかなければならないので、

スレッドの知識がない僕には何がどうなっているのか全くわからない状態でした。


Java並行処理プログラミング


そこで、スレッドで悩まないために、Java並行処理プログラミング ―その「基盤」と「最新API」を究める―を読みました。



桜庭さんの記事にもあるように4つのスレッドの制御のためにはjava.util.concurrent.BlockingQueue>T<や、java.util.concurrent.ExecutorServiceを押さえとかないと厳しいです。

僕もこの本のお陰でBlockingQueueとかがなんとなくわかるような気がしてきました。

で、やっとうまく行った実装では次のようにスレッドを作っています。


これらのスレッドにBlockingQueueで値を受け渡しさせることで同期化を図っています。


テストコード


テストコードを書く人(ユーザー)には、これらのスレッドのあたりを意識させない(隠蔽して)ようにするのが望ましい形です。

そこで、JUnitの@ClassRuleでテストクラスの実行前に準備することで、

ユーザーにはjavascriptの呼び出しだけを提供できるようにしました。

サンプルのテストコードは次のとおりです。

JsJUnitTest.groovy

package org.mikeneck.jsjunit
import org.junit.ClassRule
import org.junit.Test
import groovy.util.logging.Slf4j
import org.junit.Before
import com.sun.webpane.webkit.JSObject
/**
*
*/
@Slf4j
class JsJUnitTest {
@ClassRule
static public UseFxWebView fxWebView = UseFxWebView.defaultServer().identifiedBy('JsJUnitTest').get()
JsJUnit tester
@Before
void setUp () {
tester = fxWebView.getJsTester()
}
@Test
void testString () {
assert tester.callAsString('stringTest("test")') == 'hello test'
}
@Test
void testNumber () {
assert tester.callAsDouble('numberTest(0.1, 0.01)') == 0.11
}
@Test
void jsonTest () {
def object = tester.call('jsonTest ()') as JSObject
assert object.getMember('name') == 'name'
assert object.getMember('age') == 1
}
@Test
void personTest () {
def object = tester.call('new Person("mike")') as JSObject
assert object.getMember('name') == 'mike'
}
@Test
void personMethodTest () {
assert tester.call('(new Person("mike")).say()') == 'this is mike'
}
}


コードはGroovyで書いていますが、Javaに近い形で書いていますので、

それほど読みづらくないと思います。


テスト対象のJavascriptは次のとおりです。

test.js

var Person = function (name) {
if (this instanceof Person === false) {
return new Person(name);
}
this.name = name;
if (typeof Person.prototype.getName === "undefined") {
Person.prototype.getName = function () {
return this.name;
};
}
if (typeof Person.prototype.say === "undefined") {
Person.prototype.say = function () {
return "this is " + this.name;
};
}
};
function numberTest (arg1, arg2) {
if (typeof arg1 !== "number" && typeof arg2 !== "number") {
return 0;
} else {
return arg1 + arg2;
}
}
function stringTest (arg) {
return "hello " + arg;
}
function jsonTest () {
return {name : "name", age : 1};
}
view raw test.js hosted with ❤ by GitHub



技術的なこと


実は苦労話ばかり書いていて、技術的なことは何も触れてないですね。

そのあたりは、7/2(月)にある桜庭さん主催の『第7回 JavaFX 勉強会』でお話しようと思います。

まあ、JavaFXの話は殆どなしで、javascriptとスレッドの話になりそうですが…


TODO


一応スタティックなWebサーバーのjavascriptのテストが実行できるようになったわけですが、

まだまだ、javascriptのJavaにおける扱いに関して不明な点が多くありますし、全然型安全でありません。

また、ajaxなどのテストも書けませんし、DOMでassertする部分のサポートも不十分です。

さらにGitHubリポジトリーも作ってないですし、今のgradleスクリプトではMac以外では動きません( ー`дー´)キリッ

というわけで、次のような課題があります。

  • Windows/Linux対応
  • リポジトリ公開
  • javascript→POJOマッピング対応
  • function型の扱い
  • ServletコンテナもしくはJavaEEコンテナ搭載

このあたりはボチボチとやっていきたいと思います。

2012年6月8日金曜日

Java NIO2 をいじろう - その1

こんにちわ。みけです。

Java6のEOLが今年(2012年)の11月に迫っています。

なので、Java6を使っている人はJava7に切り替え始めましょう。

Javaの最新7の5分の1のバージョンを使っている人(つまりJava1.4を使っている人)はとっととアップデートして下さい。

Java1.4のEOLは2008年10月30日です。

Sunと契約している場合を除いて、契約していない人たちは泣き言を言わずアップデートしましょう。


本題


さて、Java7になってから登場したNIO2をちょっといじってみたいと思います。

NIO2によって、Javaではいままで扱えなかったシンボリックリンクとか、ファイルの更新日付とか、ファイルパーミッションとかが扱えるようになりました。

その中心にあるのはPathインターフェースです。

というわけで、Pathインターフェースについて、ちょっとおさらいします。

java.nio.file.Path


まあ、PathのJavadocを見ればだいたい分かるんですが、

どういう値が返ってきているかわからないので、1つずつ試してみましょう。

C:\Users\mike>groovysh
Groovy Shell (1.8.6, JVM: 1.7.0_02)
Type 'help' or '\h' for help.
-----------------------------------------------------------
groovy:000> home = System.env['HOMEPATH']
===> \Users\mike
groovy:000> dir = new File(home)
===> \Users\mike
groovy:000> path = dir.toPath()
===> \Users\mike
groovy:000> path.class
===> class sun.nio.fs.WindowsPath

まず、Pathはインターフェースなのでnew演算子でインスタンス化することは出来ません。

というわけで、java.io.File#toPath()からインスタンスを取得します。

Windows7で実行していますが、実体はsun.nio.fs.WindowsPathです。


メソッド


では、ひとつずつメソッドを確認して行きましょう。

getFileName()

Pathの名前を返します。

groovy:000> path.fileName
===> mike

getName(int index)getNameCount()

getNameCountPathを構成するPathの要素数を返します。

getNameは引数で指定された位置のPathの名前を返します。

groovy:000> Integer.metaClass.define {
groovy:001>     collect = {Closure c ->
groovy:002>         def collection = []
groovy:003>         delegate.times {
groovy:004>             collection << c(it)
groovy:005>         }
groovy:006>         return collection
groovy:007>     }
groovy:008> }
===> groovy.lang.ExpandoMetaClass@65ad9b63[class java.lang.Integer]
groovy:000> path.nameCount.collect { path.getName(it) }
===> [Users, mike]

getParent()getRoot()

まあ、名前のとおりです。

groovy:000> path.parent
===> \Users
groovy:000> path.root
===> \

isAbsolute()toAbsolutePath()

isAbsolute()はOS用のちゃんとしたPath名であるか判断します。

toAbsolutePath()はまた、OS用のちゃんとしたPath名を取得します。

プロジェクトなどでsrc/main/resourcesとやった場合などに使えそうですね。

groovy:000> path.absolute
===> false
groovy:000> path.toAbsolutePath()
===> C:\Users\mike

続きは、その2で。

2012年6月7日木曜日

Spring Roo始めました。 その3

今日は、JSR303 (Bean Validation)の話


制約を設ける


セレブが入会する会員制クラブをつくろう!としています。

そこで、メンバーテーブルを作ります。

roo> entity jpa --class org.mikeneck.roo.model.Membership --testAutomatically
Created SRC_MAIN_JAVA/org/mikeneck/roo/model
Created SRC_MAIN_JAVA/org/mikeneck/roo/model/Membership.java
Created SRC_TEST_JAVA/org/mikeneck/roo/model
Created SRC_TEST_JAVA/org/mikeneck/roo/model/MembershipDataOnDemand.java
Created SRC_TEST_JAVA/org/mikeneck/roo/model/MembershipIntegrationTest.java
Created SRC_MAIN_JAVA/org/mikeneck/roo/model/Membership_Roo_Configurable.aj
Created SRC_MAIN_JAVA/org/mikeneck/roo/model/Membership_Roo_ToString.aj
Created SRC_MAIN_JAVA/org/mikeneck/roo/model/Membership_Roo_Jpa_Entity.aj
Created SRC_MAIN_JAVA/org/mikeneck/roo/model/Membership_Roo_Jpa_ActiveRecord.aj
Created SRC_TEST_JAVA/org/mikeneck/roo/model/MembershipIntegrationTest_Roo_Configurable.aj
Created SRC_TEST_JAVA/org/mikeneck/roo/model/MembershipDataOnDemand_Roo_DataOnDemand.aj
Created SRC_TEST_JAVA/org/mikeneck/roo/model/MembershipIntegrationTest_Roo_IntegrationTest.aj
Created SRC_TEST_JAVA/org/mikeneck/roo/model/MembershipDataOnDemand_Roo_Configurable.aj
~.model.Membership roo> field string --fieldName firstName --notNull --sizeMin 2 --sizeMax 40
Updated SRC_MAIN_JAVA/org/mikeneck/roo/model/Membership.java
Updated SRC_TEST_JAVA/org/mikeneck/roo/model/MembershipDataOnDemand_Roo_DataOnDemand.aj
Created SRC_MAIN_JAVA/org/mikeneck/roo/model/Membership_Roo_JavaBean.aj
~.model.Membership roo> field string --fieldName lastName --notNull --sizeMin 3 --sizeMax 40
Updated SRC_MAIN_JAVA/org/mikeneck/roo/model/Membership.java
Updated SRC_TEST_JAVA/org/mikeneck/roo/model/MembershipDataOnDemand_Roo_DataOnDemand.aj
Updated SRC_MAIN_JAVA/org/mikeneck/roo/model/Membership_Roo_JavaBean.aj
~.model.Membership roo> field number --fieldName age --type int --min 20 --notNull
Updated SRC_MAIN_JAVA/org/mikeneck/roo/model/Membership.java
Updated SRC_TEST_JAVA/org/mikeneck/roo/model/MembershipDataOnDemand_Roo_DataOnDemand.aj
Updated SRC_MAIN_JAVA/org/mikeneck/roo/model/Membership_Roo_JavaBean.aj
~.model.Membership roo> field number --fieldName income --type long --notNull
Updated SRC_MAIN_JAVA/org/mikeneck/roo/model/Membership.java
Updated SRC_TEST_JAVA/org/mikeneck/roo/model/MembershipDataOnDemand_Roo_DataOnDemand.aj
Updated SRC_MAIN_JAVA/org/mikeneck/roo/model/Membership_Roo_JavaBean.aj
~.model.Membership roo> field date --fieldName applied --type java.util.Date --notNull --timeFormat yyyy/MM/dd --persistenceType JPA_DATE --past
Updated SRC_MAIN_JAVA/org/mikeneck/roo/model/Membership.java
Updated SRC_TEST_JAVA/org/mikeneck/roo/model/MembershipDataOnDemand_Roo_DataOnDemand.aj
Updated SRC_MAIN_JAVA/org/mikeneck/roo/model/Membership_Roo_JavaBean.aj
view raw log.roo hosted with ❤ by GitHub


基本的な制約をここでかけています。

  • 名前
    • not null / 最低2文字 / 最大40文字
  • 姓名
    • not null / 最低3文字 / 最大40文字
  • 年齢
    • 最低20 / not null
  • 収入
    • not null
  • 会員登録日
    • 過去

出来上がったMembershipクラスは次のようなコードになっています。

package org.mikeneck.roo.model;
import java.util.Date;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Past;
import javax.validation.constraints.Size;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.roo.addon.javabean.RooJavaBean;
import org.springframework.roo.addon.jpa.activerecord.RooJpaActiveRecord;
import org.springframework.roo.addon.tostring.RooToString;
@RooJavaBean
@RooToString
@RooJpaActiveRecord
public class Membership {
@NotNull
@Size(min = 2, max = 40)
private String firstName;
@NotNull
@Size(min = 3, max = 40)
private String lastName;
@NotNull
@Min(20L)
private int age;
@NotNull
private long income;
@NotNull
@Past
@Temporal(TemporalType.DATE)
@DateTimeFormat(style = "M-")
private Date applied;
}


この段階で自動生成されたテストを流します。



自動生成されたテストのデータというのは、Spring Rooが出力したテストデータ生成用のクラスMembershipDataOnDemandクラスによって作成されます。


ビジネス的な制約の導入


ところでセレブがくる会員制クラブですので、若いヤンキーニーチャンを入会させたくありません。

そこで、30歳未満の人には高めの収入(100,000ドル以上)を持っていることを制約条件に加えようと思います。

まず、テストに上の条件のユーザーの制約を書いてみます。

package org.mikeneck.roo.model;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestName;
import org.springframework.roo.addon.test.RooIntegrationTest;
import org.springframework.transaction.annotation.Transactional;
import javax.validation.ConstraintViolationException;
import java.util.Date;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
@RooIntegrationTest(entity = Membership.class)
public class MembershipIntegrationTest {
@Rule
public TestName testName = new TestName();
@Transactional
@Test (expected = ConstraintViolationException.class)
public void youngMemberCannotBeApplied () {
Membership membership = new Membership();
membership.setFirstName("mike");
membership.setLastName("neck");
membership.setAge(29);
membership.setIncome(99999);
membership.setApplied(now());
membership.persist();
fail(testName.getMethodName());
}
@Transactional
@Test
public void youngMemberWithALotOfIncomeCanBeApplied () {
Membership membership = new Membership();
membership.setFirstName("mike");
membership.setLastName("neck");
membership.setAge(29);
membership.setIncome(100000);
membership.setApplied(now());
membership.persist();
membership.flush();
membership.clear();
Long id = membership.getId();
Membership stored = Membership.findMembership(id);
assertThat(testName.getMethodName(), stored, is(not(nullValue())));
assertThat(testName.getMethodName(), stored.getFirstName(), is("mike"));
}
private Date now () {
long time = new Date().getTime() - 200L;
return new Date(time);
}
}


テストを流します。



30歳未満で収入の低い人が入会できないことを確認するテストyoungMemberCannotBeAppliedが落ちていることがわかります。

では、この条件を実装していきます。

ここで使うのがJSR303のBean Validation APIの@AssertTrueです。

@AssertTrueは指定したフィールドまたはメソッドがtrueを返すことを強制する制約です。

これを用いて条件を実装します。

package org.mikeneck.roo.model;
import java.util.Date;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.validation.constraints.*;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.roo.addon.javabean.RooJavaBean;
import org.springframework.roo.addon.jpa.activerecord.RooJpaActiveRecord;
import org.springframework.roo.addon.tostring.RooToString;
@RooJavaBean
@RooToString
@RooJpaActiveRecord
public class Membership {
@NotNull
@Size(min = 2, max = 40)
private String firstName;
@NotNull
@Size(min = 3, max = 40)
private String lastName;
@NotNull
@Min(20L)
private int age;
@NotNull
private long income;
@NotNull
@Past
@Temporal(TemporalType.DATE)
@DateTimeFormat(style = "M-")
private Date applied;
@AssertTrue (message = "A young person without enough income cannot be applied.")
public boolean isSatisfiedMembershipCondition () {
if (age < 30) {
return income >= 100000;
} else {
return true;
}
}
}


では確認のためにテストを流しましょう。



追加したテストの方は通ったようですが、あれれ、自動生成されたテストは軒並み落ちていますね。

まあ、勝手に追加した条件なのでSpring Rooの方では検知できないのでしょう。よく考えればそうですね。


git


んで、よく見てみると、モデルに変更を加えた後になぜかAspectJのコードが変化しているようです。



何が変わったのでしょうか?





40文字以上だったら40文字に直してくれてたコードが、直してくれなくなっていますね。

また、年齢が20未満だったら20に直してくれていたコードが直してくれなくなっていますね。

日付についても現在より10,000,000Lだけ前に修正してくれていたコードが現在時間+αになるように変更されています。

Spring Roo君はなんてことをしてくれるんだ!


Push in


こうなったら、AspectJのコードをJavaの方にPush Inして調整する必要があるようです。

変更されてしまったメソッドにカーソルを当てた状態で、IntelliJ IDEAのRefactorメニューからPush ITDs Inを選択します。

(eclipse…知らん…)

その後、MembershipDataOnDemand.javaのPush Inされたメソッドをテストが通る(と言うよりはビジネス的に問題のないデータが提供される)ように修正します。

package org.mikeneck.roo.model;
import org.springframework.roo.addon.dod.RooDataOnDemand;
import java.util.Date;
@RooDataOnDemand(entity = Membership.class)
public class MembershipDataOnDemand {
public void setAge(Membership obj, int index) {
int age = index;
if (age < 30) {
age = 30;
}
obj.setAge(age);
}
public void setApplied(Membership obj, int index) {
long time = new Date().getTime() - (index * 100L + 100L);
obj.setApplied(new Date(time));
}
public void setFirstName(Membership obj, int index) {
String firstName = "firstName_" + index;
if (firstName.length() > 40) {
firstName = firstName.substring(40);
}
obj.setFirstName(firstName);
}
public void setLastName(Membership obj, int index) {
String lastName = "lastName_" + index;
if (lastName.length() > 40) {
lastName = lastName.substring(40);
}
obj.setLastName(lastName);
}
}


では再度テストを実行してみます。



はい、通りました。

結論


はい、Spring RooのBean Validation API関連の作業について見てきました。

多少面倒なところはあるもののテストデータを自動で生成してくれたり、テストを自動で生成してくれているところは助かります。

ただ、複数のフィールドにまたがるビジネス上の制約についてはRooはアホなくらい鈍感ですね。

このあたりは慣れるしかなさそうです。

2012年6月5日火曜日

"fizzになる数字をn個挙げる"をGroovyで

@irofさんのホームページの課題『"Buzzになる数字をn個挙げる"をGroovyで…やろうと思ったんだけど』

やってみました。

IntRangeとかInteger#timesとか使ったほうがいいんだけど、無駄な計算しているよなということで、

結局Integer.metaClassをいじってしまいました。

def fizzbuzz = {final int arg ->
def res = (arg % 3 == 0? 'fizz' : '' ) + (arg % 5 == 0? 'buzz' : '' )
res += res.size() == 0? arg : ''
res
}
Integer.metaClass.define {
items = {Closure c ->
final int num = delegate
def results = []
def i = 0
while(num > results.size()) {
if (c(i)) {
results << i
}
i += 1
}
results
}
}
println 5.items {
fizzbuzz(it) == 'fizz'
}


結果

[3, 6, 9, 12, 18]
view raw result.txt hosted with ❤ by GitHub


多分、もっといい書き方はきょんくんがやってくれるでしょう。

Spring Roo初めてみました。 その2

プロジェクトの作り方。


以下のコマンドを打つべし。

01 project --topLevelPackage your.app.pkg --projectName pjname

データベースの設定


以下のコマンドを打つべし。

01 jpa setup --database MYSQL --provider HIBERNATE

データベースで設定できるのは次のデータベースたち

  • DATABASE_DOT_COM
  • DB2_400
  • DB2_EXPRESS_C
  • DERBY_CLIENT
  • DERBY_EMBEDDED
  • FIREBIRD
  • GOOGLE_APP_ENGINE
  • H2_IN_MEMORY
  • HYPERSONIC_IN_MEMORY
  • HYPERSONIC_PERSISTENT
  • MSSQL
  • MYSQL
  • ORACLE
  • POSTGRES
  • SYBASE

JPAのプロバイダーで設定できるのは次のプロバイダーたち

  • DATANUCLEUS
  • ECLIPSELINK
  • HIBERNATE
  • OPENJPA

その他の設定可能なオプションたち

  • applicationId
    • Google App Engine用のオプション。GAEのapplication idを設定します。
  • hostName, databaseName, userName, password
    • データベースの場所や名前、スキーマ名やパスワードを設定します。
  • jndiDataSource
    • JDBCでなく、JavaEEサーバーが提供するJNDIを用いる場合にJNDI名を指定します。
  • persistenceUnit, transactionManager
    • transactionManagerはSpringのどのtransactionManagerを使うかを設定します。persistenceUnitはJPAの環境を指定します…よくわかっていないorz


なお、Spring Rooでのjpa setupコマンドは何度でも打ち直すことが可能で、そのたびにデータベースの設定を変えられます。

開発環境では開発環境用の設定をして、実際に動かす環境ではまた別の設定をするということが可能です。

なお、Google App Engineで利用する場合は、providerDATANUCLEUSだけだそうです。

Spring Roo初めてみました。

久々にブログ書いています。


みけです。


Spring Rooとは


CUIベースでアプリを作っていけるツールです。

JavaのRADツールですね。

RubyにはRoR、GroovyにはGrailsがあるように、JavaでもRADしたいという思いから生まれたようです。


なんで?


日本ではあまりやる人いないようなので、

まあ、ニッチなところを目指して初めてみました。

書籍『Spring Roo in Action』 も手に入ったことですし。


どんな感じ?


とりあえず、次のようなモデルを作ってみました。

  • タスクを管理するモデル
  • タイトル : 文字列
  • 詳細 : 文字列
  • 終わった? : boolean

Rooだと以下のようなコマンドを入力します。

entity jpa --class ~.model.Task -- testAutomatically
field string --fieldName title --notNull --sizeMax 40
field string --fieldName description --notNull --sizeMax 140
field boolean --fieldName completed --value false
view raw log.roo hosted with ❤ by GitHub


これにより、作成されるファイルが、javaファイル1つと、aspectJのファイルが5つです。

package org.mikeneck.roo.taskmanager.model;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.roo.addon.javabean.RooJavaBean;
import org.springframework.roo.addon.jpa.activerecord.RooJpaActiveRecord;
import org.springframework.roo.addon.tostring.RooToString;
@RooJavaBean
@RooToString
@RooJpaActiveRecord
public class Task {
@NotNull
@Size(max = 40)
private String name;
@NotNull
@Size(max = 140)
private String description;
@Value("false")
private Boolean completed;
}
view raw Task.java hosted with ❤ by GitHub


// WARNING: DO NOT EDIT THIS FILE. THIS FILE IS MANAGED BY SPRING ROO.
// You may push code into the target .java compilation unit if you wish to edit any member(s).
package org.mikeneck.roo.taskmanager.model;
import org.mikeneck.roo.taskmanager.model.Task;
import org.springframework.beans.factory.annotation.Configurable;
privileged aspect Task_Roo_Configurable {
declare @type: Task: @Configurable;
}


// WARNING: DO NOT EDIT THIS FILE. THIS FILE IS MANAGED BY SPRING ROO.
// You may push code into the target .java compilation unit if you wish to edit any member(s).
package org.mikeneck.roo.taskmanager.model;
import org.mikeneck.roo.taskmanager.model.Task;
privileged aspect Task_Roo_JavaBean {
public String Task.getName() {
return this.name;
}
public void Task.setName(String name) {
this.name = name;
}
public String Task.getDescription() {
return this.description;
}
public void Task.setDescription(String description) {
this.description = description;
}
public Boolean Task.getCompleted() {
return this.completed;
}
public void Task.setCompleted(Boolean completed) {
this.completed = completed;
}
}


// WARNING: DO NOT EDIT THIS FILE. THIS FILE IS MANAGED BY SPRING ROO.
// You may push code into the target .java compilation unit if you wish to edit any member(s).
package org.mikeneck.roo.taskmanager.model;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import org.mikeneck.roo.taskmanager.model.Task;
import org.springframework.transaction.annotation.Transactional;
privileged aspect Task_Roo_Jpa_ActiveRecord {
@PersistenceContext
transient EntityManager Task.entityManager;
public static final EntityManager Task.entityManager() {
EntityManager em = new Task().entityManager;
if (em == null) throw new IllegalStateException("Entity manager has not been injected (is the Spring Aspects JAR configured as an AJC/AJDT aspects library?)");
return em;
}
public static long Task.countTasks() {
return entityManager().createQuery("SELECT COUNT(o) FROM Task o", Long.class).getSingleResult();
}
public static List<Task> Task.findAllTasks() {
return entityManager().createQuery("SELECT o FROM Task o", Task.class).getResultList();
}
public static Task Task.findTask(Long id) {
if (id == null) return null;
return entityManager().find(Task.class, id);
}
public static List<Task> Task.findTaskEntries(int firstResult, int maxResults) {
return entityManager().createQuery("SELECT o FROM Task o", Task.class).setFirstResult(firstResult).setMaxResults(maxResults).getResultList();
}
@Transactional
public void Task.persist() {
if (this.entityManager == null) this.entityManager = entityManager();
this.entityManager.persist(this);
}
@Transactional
public void Task.remove() {
if (this.entityManager == null) this.entityManager = entityManager();
if (this.entityManager.contains(this)) {
this.entityManager.remove(this);
} else {
Task attached = Task.findTask(this.id);
this.entityManager.remove(attached);
}
}
@Transactional
public void Task.flush() {
if (this.entityManager == null) this.entityManager = entityManager();
this.entityManager.flush();
}
@Transactional
public void Task.clear() {
if (this.entityManager == null) this.entityManager = entityManager();
this.entityManager.clear();
}
@Transactional
public Task Task.merge() {
if (this.entityManager == null) this.entityManager = entityManager();
Task merged = this.entityManager.merge(this);
this.entityManager.flush();
return merged;
}
}


// WARNING: DO NOT EDIT THIS FILE. THIS FILE IS MANAGED BY SPRING ROO.
// You may push code into the target .java compilation unit if you wish to edit any member(s).
package org.mikeneck.roo.taskmanager.model;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Version;
import org.mikeneck.roo.taskmanager.model.Task;
privileged aspect Task_Roo_Jpa_Entity {
declare @type: Task: @Entity;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "id")
private Long Task.id;
@Version
@Column(name = "version")
private Integer Task.version;
public Long Task.getId() {
return this.id;
}
public void Task.setId(Long id) {
this.id = id;
}
public Integer Task.getVersion() {
return this.version;
}
public void Task.setVersion(Integer version) {
this.version = version;
}
}


// WARNING: DO NOT EDIT THIS FILE. THIS FILE IS MANAGED BY SPRING ROO.
// You may push code into the target .java compilation unit if you wish to edit any member(s).
package org.mikeneck.roo.taskmanager.model;
import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.mikeneck.roo.taskmanager.model.Task;
privileged aspect Task_Roo_ToString {
public String Task.toString() {
return ReflectionToStringBuilder.toString(this, ToStringStyle.SHORT_PREFIX_STYLE);
}
}


Spring Rooでは、Javaでコードを書くときに冗長になりがちな部分を単純にして、Javaのコードをすっきりさせるそうですが、

うむ、AspectJのコードだらけでんな。


僕はこのあたりはGrailsの方が好きだったりします。

Grailsのモデルなら、こんな感じでしょうか…

package org.mikeneck.roo.taskmanager.model
import groovy.transform.Canonical
@Canonical
class Task {
String title
String description
boolean completed = false
static constraints = {
title maxSize : 40, blank : false
description maxSize : 140. blank : false
}
}
view raw Task.groovy hosted with ❤ by GitHub


仕組み


AspectJもGroovyもコンパイル時にやっていることはだいたい同じで、

AspectJがコンパイル時にJavaコードにウィービングするのと同様に、

Groovyではコンパイル時にAST変換します。

RooでのコンパイルではAspectJによって、getter/setterやtoStringがJavaに入れられてからコンパイルされるのと同様に、

Groovyではgetter/setterやtoStringを一度生成してから、コンパイルされます。


まあ、まだ両方とも全然さわれていないので、また何かわかったら書こうかな…

おわり。