My Photo

October 07, 2013

正規表現メモ

正規表現についてのメモ。確認はPHPのPCRE関数と秀丸エディタ(Ver.8.13)の検索ダイアログで行った。
それ以外の処理系(JavaScript、Javaなど)でも大体同じように使えると思う。

グループ化、非キャプチャグループ

正規表現を "(" と ")" で囲んでグループ化することができる。例えば "ab" が1回以上繰り返しさらに "cd" が1回以上繰り返すという正規表現は "(ab)+(cd)+" となる。
グループ化した部分にマッチする文字列は、グループより後ろの部分で\1,\2,\3,etc...の形式で後方で参照できる。"\"の後ろの数字はグループ化された順番。

正規表現 意味 文字列 マッチする部分
(ab)+(cd)+ "ab" が1回以上繰り返し
その後に"cd"が1回以上繰り返す。
ababcdcdcd ababacdcdcd
([a-zA-Z]+)
(\d+)-\2\1
英字1個以、次に数字1個以上、次にハイフン、
次に前に現れたのと同じ数字1個以上の文字列、 最後に前に出現した英字1個以上の文字列
abc123-123abc,xyz789-xyz789 abc123-123abc

また、グループ化されたものは\1,\2,\3,etc...の形式で後方で参照できる。
"(?:" と ")" で囲めばグループ化はされるが、後方参照はできなくなる。これを非キャプチャグループと呼ぶ。後方参照が不要な場合はこれを用いてもよい。
非キャプチャグループについては検索して調べると、非キャプチャグループの方が単なるグループより動作が速いと記述も変わらないという記述もある。

OR条件

正規表現で "|" はOR(論理和)を表している。
単純に "ABまたはCD" という意味の正規表現は "AB|CD" でよい。"(AB|CD)" や "((AB)|(CD))" のようにグループ化する必要ない。
しかし、"ABC123" または "ABC456" という風に共通部分があって一部がOR条件の場合は、"ABC(123|456)" のようにグループ化する必要がある。グループ化せずに "ABC123|456" では「"ABC123" または "456"」という意味になる。

後方一致指定、後方不一致指定、前方一致指定、前方不一致指定

後方一致指定(肯定先読み)
特定のパターンの後にさらに特定のパターンが続くことを表すアンカー。
"(?=<パターン>)" と記述する。
後方不一致指定(否定先読み)
特定のパターンの後にさらに特定のパターンが続かないことを表すアンカー
"(?!<パターン>)" と記述する。
前方一致指定(肯定後読み)
特定のパターンの前にさらに特定のパターンが存在することを表すアンカー
"(?<=<パターン>)" と記述する。
前方不一致指定(否定後読み)
特定のパターンの前にさらに特定のパターンが存在しないことを表すアンカー
"(?<!<パターン>)" と記述する。
正規表現 意味 文字列 マッチする部分
abc(?=xyz) 後ろに"xyz"が来る"abc" abcxyz abc
abc(?!xyz) 後ろに"xyz"以外のパターンが来る"abc" abcijk abc
(?<=abc)xyz 前に"abc"がある"xyz" abcxyz xyz
(?<!abc)xyz 前に"abc"以外のパターンがある"xyz" lmnxyz xyz

これらの正規表現はアンカーであり、マッチする文字列には含まれない。アンカーとは位置を指定する正規表現である。
#他のアンカーには "^" や "$" などがある。
また、これらの正規表現の後に正規表現を続けると、AND条件になる。例を示す。

正規表現 意味 文字列 マッチする部分
abc(?=\w+) 後ろに単語が続く"abc" abcdef123 abc
abc(?=\w+)[a-z]+ abcdef123 abcdef
正規表現"abc(?=\d+)[^a-z] abcdef123 マッチしない
2014/06/16追記:JavaScriptの正規表現では前方一致指定と前方不一致指定が使えない。

\w

"\w" は単語文字の文字クラス。半角の単語文字は英数字とアンダースコア、と思っていたら、処理系やロケールによっては2バイト文字やアルファベット以外の文字も含まれるらしい。
「"\w" と "[:alnum:]" は同じ」という明らかに間違った説明をしているサイトがいくつかあった。何かのドキュメントに「"\w" と "[:alnum:]" は同じ」とあって、それをもとにして書かれた内容なのかなと思う。

秀丸エディタの検索ダイアログとマクロの検索コマンドの違い

秀丸エディタの検索ダイアログで正規表現を使った検索する場合、「正規表現」だけをチェックして実行すると大文字と小文字の違いを無視する。大文字と小文字を区別したい場合は「大文字/小文字の区別」にもチェックを入れる。
一方、マクロでの検索系コマンド(searchdownなど)では "regular" オプションを付ければ大文字と小文字を区別する。

searchdown <検索文字列>, regular;
"regular" があれば "casesense" はあってもなくても同じ。"regular" かつ "nocasesense" の場合は大文字と小文字を区別しない。
searchdown <検索文字列>, regular, nocasesense;
同じ検索処理なのに検索ダイアログとマクロで使い方の統一性がない。

秀丸エディタの正規表現の注意事項

秀丸エディタの検索・置換・マクロで使う正規表現(以下、「秀丸Regexp」と呼称)と自分がよく使うPHPのPCRE関数や、その他なプログラミング言語の正規表現(以下、「一般Regexp」と呼称)は大体は同じだが、一部独特なところがあるのでメモっておく。
一般Regexpでは前に書いたように "\w" は英数字とアンダースコアを含む文字クラスである。
#それ以外の文字を含む場合もある。
秀丸Regexpでは "\w" は英字とアンダースコアであり、数字を含まない。一般Regexpの "\w" にあたる英数字とアンダースコアからなる文字クラスは "\c" である。
また、一般Regexpでは "\b" は空白文字の文字クラスワード境界のアンカーまたはバックスペース文字であるが、秀丸エディタではバックスペース文字という意味しかない。一般Regexpで "\b" がアンカーがバックスペースかの区別は、角括弧("[]")で囲まれた文字クラスの中ではバックスペース、文字クラスの外ではアンカーである。秀丸エディタには空白文字のもクラスがないが、"\<" と "\>" という英単語の始まり・終わりのアンカーがある。
例として、下記の文字列を考える。

_ab3 C_D4,eF_5

この文字列の単語を検索したい。つまり "_ab3", "C_D4", "eF_5" の3つがマッチするように検索したい。
PHPのPCRE関数であれば以下のコードでできる。難しくはない。

preg_match_all('/\b\w+\b/', "_ab3 C_D4,eF_5", $matches);

秀丸Regexpで同じことをする正規表現は、結構考えてしまった。文字クラス "\b" は使えない。
"\c+" では単語全体だけではなく単語の部分文字列("ab3", "b3", "3", "_D4",etc...) にもマッチする。
"\<\c+\>" では、"\<""\>" が「単語」の始まり・終わりではなく「英単語」の始まり・終わりなので、数字を除いた "_ab", "C_D", "eF_" にマッチする。
"(?<=[^\c])\c+(?=[^\c])" とすると行頭の単語以外はマッチする
"(?<=^|[^\c])\c+(?=[^\c])" で行頭の単語を含めてすべての単語にマッチする。期待した動作だ。前方一致指定の中に行頭を表すアンカー "^" が入っているのが若干変な気もするが。

以前の記事の補足説明

以前に秀丸エディタの正規表現について書いた記事で例として挙げた下記の正規表現について、説明不足だったのでここで説明する。
下記の正規表現は "all""see" などの「1文字目≠2文字目かつ2文字目=3文字目の3文字の英単語」にマッチする。

\<([a-z])((?!\1)[a-z])\2\>

・先頭の "\<" と末尾の "\>" はそれぞれ単語の始まり、終わりを示す。これらがあるため、例えば3文字の単語にはマッチするが4文字以上の単語の一部分の3文字("tall" や "seem" など)にはマッチしない。
"([a-z])" はアルファベット小文字の1文字。後方参照のためグループ化してある。
"((?!\1)[a-z])" は後方不一致指定(否定先読み)を使っている。"\1" は前のグループ、つまりこの部分の前にあるアルファベット小文字1文字である。"(?!\1)" はその1文字ではないものが後にあるというアンカー。"[a-z]" はアルファベット小文字1文字であり、後方不一致指定の後にパターンがある場合は、後方不一致指定とそのパターンがAND条件となる。よって、"(?!\1)[a-z]" は「前のアルファベット小文字1文字とは異なるアルファベット小文字1文字」となる。アンカーである後方不一致指定だけではなく後に "[a-z]" があるため1文字とマッチし、後方参照のためにグループ化されている。
"\2" は前のグループ、"(?!\1)[a-z]" にマッチしたアルファベット小文字1文字である。
・以上から、全体としては「1文字目と2文字が異なり、2文字目と3文字が同じである3文字のアルファベット小文字の単語」にマッチする。

参考文献:秀丸エディタヘルプ

参考サイト:
正規表現言語 - クイック リファレンス
Windowsユーザーに教えるLinuxの常識(6):使うほどに良さが分かる正規表現 (2/2) - @IT
正規表現(肯定先読み、否定先読み、肯定戻り読み、否定戻り読み) - satosystemsの日記:
正規表現の先読み・後読みを極める! - あらびき日記

November 18, 2012

Androidアプリ開発メモ068:Tweenアニメーション

Viewに対して回転やサイズ変更などをすることでアニメーションを作成する。
下記のクラスを利用できるが、クラスを直接利用するのではなくXMLでリソースファイルとして定義してプログラムに読み込んで利用することがほとんど。

クラス説明XML要素
AlphaAnimationアルファ値変更<alpha>
RotateAnimation回転<rotate>
ScaleAnimation拡大・縮小<scale>
TranslateAnimation移動・変形<translate>

上表のクラスは android.view.animation.Animationクラスを継承している。

アニメーションの定義の例。下記の定義はだんだん濃くなりながら回転していくアニメーションである。
ファイル名は制限はない。例えば tween.xml とかにしておく。

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
     android:interpolator="@android:anim/accelerate_interpolator">
    <alpha android:fromAlpha="0.0" android:toAlpha="1.0" 
           android:duration="3000" />
    <rotate android:fromDegrees="0" android:toDegrees="360"
            android:pivotX="50%" android:pivotY="50%"
            android:duration="3000" />
</set></pre>

android:interpolator属性はアニメーションの変化の割合を指定する。値は下表参照。

属性説明
@android:anim/accelerate_interpolator動きがだんだん速くなる
@android:anim/accelerate_decelerate_interpolator動きがだんだん遅くなる

属性にはalpha、rotate、scale、translateの各要素に固有のものと、共通のものがある。それぞれの意味は参考ページを参照。

下記は1秒で縦に縮んで、2秒で元に戻るアニメーションの例。
android:startOffset属性を使って、前半のscale要素で定義した動きが終わってから後半のscale要素で定義した動きが始まるように、後半のscale要素にはandroid:startOffset属性を持たせてある。

<set xmlns:android="http://schemas.android.com/apk/res/android"
     android:interpolator="@android:anim/accelerate_interpolator"
     android:shareInterpolator="false"
     android:fillAfter="true" >
    <scale android:fromXScale="1.0" android:toXScale="1.0"
           android:fromYScale="1.0" android:toYScale="0.5"
           android:pivotX="50%" android:pivotY="50%"
           android:duration="1000" />
    <set android:interpolator="@android:anim/accelerate_interpolator">
        <scale android:fromXScale="1.0" android:toXScale="1.0"
               android:fromYScale="1.0" android:toYScale="2.0"
               android:pivotX="50%" android:pivotY="50%"
               android:startOffset="1000" android:duration="2000" />
    </set>
</set>

上記の例で注意点が2つ。
まず、アニーメーションが終わった後そのままの状態を保つためにandroid:fillAfter属性をtrueとしているが、この属性はscale要素など個々の動きを定義する要素に設定しても効かない。Tweenアニメーションの定義のトップレベルのset要素に設定しないと意味がないようだ。
また、後半のscale要素のY軸方向の値は1.0~2.0としている。縮めた物体を元に戻すなら0.5~1.0のような気がするが、それでは思ったようなアニメーションにはならない。
scale要素のandroid:fromXScale属性などの値はそこで定義されたアニメーションが始まる時点の大きさが基準となるからである。
0.5~1.0では前半の動きで既に大きさの0.5倍になっているところからさらに0.5倍(元の大きさの0.25倍)からはじまって、1倍(0.5×1.0で元の0.5倍)の大きさで終わってしまう。1.0~2.0なら元の大きさの0.5倍(0.5×1.0)から始まって、元の大きさ(0.5×2.0)となる。

tweenアニメーションのリソースファイルを読み込んで実行する onCreate() の例。

public void onCreate(Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);
	setContentView(R.layout.main);
	ImageView img = (ImageView)findViewById(R.id.test_image);
	Animation animation = AnimationUtils.loadAnimation(this, R.anim.tween);
	img.startAnimation(animation);
}

参考文献:Androidアプリケーション開発標準資格教科書 10-5 NDKの概要

参考ページ:
7.5.1 アニメーションリソース - ソフトウェア技術ドキュメントを勝手に翻訳
Viewにアニメーションを付与する(Tweenアニメーション) << Tech Booster
Androidアプリのアニメーションリソースの使い方、定義方法 | mucchinのAndroid戦記

September 10, 2012

Androidアプリ開発メモ067:NDKのサンプルを動かしてみた

NDKのサンプル「com.example.hellojni.HelloJni」を動かしてみた。

WindowsでNDKを利用するにはCygwinが必要というので、まずCygwinをダウンロードしてインストール。Cygwinのバージョンは1.7.16。
#セットアップファイルがsetup.exeって嫌だなあ。ファイル名で何のインストールをするものなのかわからない。
インストールするパッケージはとりあえず参考ページ3を見てgcc4、make、gdblibexpat1を選択。依存関係で必要なものは「これも必要だけどインストールするか?」って聞かれるので一緒に入れる。
インストールが終わってスタートメニューかデスクトップのアイコンから起動すると /home の下にユーザのディレクトリ作られる。
あと、必要かどうかわからないが、Windowsのユーザ環境変数に、名前はHOMEで値は"/home/<ユーザ名>"を追加。

Cygwinのインストールが終わったらNDKをダウンロードして展開。NDKのリビジョンはRevision 8b。
Cygwinの /home/<ユーザ名>、Windowsから見れば <Cygwinのフォルダ>\home\<ユーザ名> に展開。
展開場所はCygwinのフォルダの配下じゃなくてもどこでもいいだんろうが、Cygwin配下ではない場合はパスが "/cygdrive/<ドライブ>/..."となってなんかかっこ悪い。せっかくCygwinを入れたのだからUNIXっぽいパスにしたかったので。
それから.bash_profileのPATHにNDKのディレクトリを追加。
これで準備完了。

Eclipseでサンプルのプロジェクトを作成する。新規>その他で「Android Project from Existing Code」で <NDKのフォルダ>\samples\hello-jni を選択。
アプリとプロジェクトとtestのプロジェクトが出てくる。アプリだけでいいんだろうけど、とりあえず両方選んでみた。
あとは参考サイト1の通り。Cygwinでhello-jniディレクトリに移動し、ndk-buildを実行すると共有ライブラリファイルlibhello-jnai.soが作成さる。

into@note02 ~
$ cd android-ndk-r8b/

into@note02 ~/android-ndk-r8b
$ cd samples/

into@note02 ~/android-ndk-r8b/samples
$ cd hello-jni/

into@note02 ~/android-ndk-r8b/samples/hello-jni
$ ndk-build
Gdbserver      : [arm-linux-androideabi-4.6] libs/armeabi/gdbserver
Gdbsetup       : libs/armeabi/gdb.setup
Cygwin         : Generating dependency file converter script
Compile thumb  : hello-jni <= hello-jni.c
SharedLibrary  : libhello-jni.so
Install        : libhello-jni.so => libs/armeabi/libhello-jni.so

libhello-jnai.so ができたらEclipseでプロジェクトを最新の状態で更新する。
これで通常のAndroidアプリケーションと同様にAVD上や実機上で実行できる。

参考文献:Androidアプリケーション開発標準資格教科書 10-5 NDKの概要

参考ページ:
1.Android NDKサンプルプログラムのビルドについて~その2~: TBヘッドライン
2.AndroidのNDK 1.5でHelloJNIを動かす手順 - Android(アンドロイド)情報-ブリリアントサービス
3.Android NDK開発環境の設定

August 29, 2012

JNI その2:ネイティブコードからJavaメソッドを呼び出す

前記事で参考にしたのネイティブコードからJavaのメソッドを呼ぶサンプルコードが
Javaからネイティブコードを呼んでそこからさらにJavaメソッドを呼ぶ
というなんとも妙もので、実際に動かす例としてはイマイチと思った。
参考にならなそうなので、WEBでJNI関係の記事を検索して主に参考ページ1を参考にした。というか、ほぼそのまま。ただ、C++はわからないのでCでコンパイルできるようにネイティブのコードを少し変えた。

前記事と同じくWindows上で実施。ボーランドC++コンパイラを使用。
作業フォルダはC:\work。
下記のフォルダ、ファイルのツリー表示で括弧で囲ってあるファイルはツール(javac、javah、bcc32、implib)が出力するファイル。

作業フォルダ
│  JNIHello2.c
│  [JNIHello2.exe]
│  [JNIHello2.obj]
│  [JNIHello2.tds]
│  [jvm_bcc32.lib]
│
└─jp
    └─jnisample
            [JNIHello2.class]
            JNIHello2.java

  1. Javaのコードを作成
  2. DLLからlibを作成
  3. ネイティブコードを作成し実行ファイルの作成
  4. 実行

Javaコードの作成

Javaのクラス、メソッドはネイティブから呼び出すからといって特別なことは特にない。
ただ、staticメソッドの方がクラスを生成しなくて言い分簡単なのでこの例ではstaticメソッドにした。
参考ページ1のJavaコードとの違いは、パッケージをデフォルトパッケージから適当に作ったパッケージ(jp.jnisample)に変えただけ。

package jp.jnisample;

public class JNIHello2 {
	public static String getMessage(){
		return "Hello World";
	}
}

作業フォルダでjavacコマンドを実行しコードをコンパイルする。

C:\work>javac jp\jnisample\JNIHello2.java

DLLからlibを作成

参考ページ1によると、VCならばJava付属のjvm.dllを使えばよいが、ボーランドC++コンパイラの場合は jvm.dll からボーランドC++コンパイラ付属のimplibというツールを使ってlibファイルを作成しそれを使わないといけないそうだ。
下記のようにして jvm_bcc32.lib というlibファイルを作った。

C:\work>implib jvm_bcc32.lib "c:\Program Files\Java\jdk1.6.0_24\jre\bin\client\jvm.dll"

Borland Implib Version 3.0.22 Copyright (c) 1991, 2000 Inprise Corporation


ネイティブコードを作成し実行ファイルの作成

はじめの方に書いたが、参考ページ1のネイティブのコードはC++で書かれていて、自分はC++がわからない。よってCのコードとしてコンパイルできるように、ファイル名の拡張子をcに変更し、内容にも若干の修正をした。
#拡張子がcかcppかでボーランドC++コンパイラはC/C++の判定をしているんだろうか? ネイティブコードの修正点は修正点は以下。

  • 変数宣言の場所を変えた。C++ではどこでも変数宣言できるが、Cはブロックの先頭で宣言しなければならない。
  • ポインタ変数使い方。正直、忘れてる。とりあえず「env->」と書いてあった場所を「(*env)->」に書き換えた。
  • JNI関数を呼ぶときに引数を足した。(*env)->で呼ばれる関数にはenvを、(*vm)->で呼ばれる関数にはvmを第1引数として加えた。
#include <stdio.h>
#include <jni.h>

int main(){
	JNIEnv *env;
	JavaVM *vm;
	JavaVMInitArgs vm_args;
	JavaVMOption options[1];	//JVMオプション配列
	
	int res;
	jclass cls;
	jmethodID mid;
	jstring str;
	
//	options[0].optionString = "-Djava.class.path=.\\classes";	/* クラスパス */
	options[0].optionString = "-Djava.class.path=.";	/* クラスパス */

	vm_args.version = JNI_VERSION_1_6;
	vm_args.options = options;
	vm_args.nOptions = 1;    //JVMオプション個数
	vm_args.ignoreUnrecognized = 1;
//	vm_args.ignoreUnrecognized = TRUE;	// TRUEだとwindows.hのインクルードが必要になる

	//JVMの作成
	res = JNI_CreateJavaVM(&vm, (void **)&env, &vm_args);

	if (res < 0) {
		printf("Fail to create JVM.\n");
		return(1);
	}
	//JNIHello2.classの取得(クラスパスより検索)
	cls = (*env)->FindClass(env, "jp/jnisample/JNIHello2");
	if (cls == 0) {
		printf("Fail to find class JNIHello2\n");
		return(1);
	}

	//取得したクラスからgetMessageというstaticメソッドを取得
	mid = (*env)->GetStaticMethodID(
			env, cls, "getMessage",
			"()Ljava/lang/String;");
	//メソッドを実行し、戻り値Stringを受け取る。
	str = (jstring)(*env)->CallStaticObjectMethod(env, cls, mid);
	//UTFのchar配列に変換後、コンソールに関数の戻り値を出力。
	printf("[%s]\n", (*env)->GetStringUTFChars(env, str, NULL));

	(*vm)->DestroyJavaVM(vm);

	return 0;
}

これをコンパイル、リンクして実行ファイルを作成。

C:\work>bcc32 -L jvm_bcc32.lib JNIHello2.c
Borland C++ 5.5.1 for Win32 Copyright (c) 1993, 2000 Borland
JNIHello2.c:
Turbo Incremental Link 5.00 Copyright (c) 1997, 2000 Borland


実行

実行するにはパスを追加しなければならない。自分の環境では
C:\Program Files\Java\jdk1.6.0_24\jre\bin\client
を環境変数PATHに追加。
これは、さきほど jvm_bcc32.lib を作るのに使った jvm.dll のある場所にパスが通っていないといけないということだろうか?

パスを追加すると、下記のように実行できた。

C:\work>JNIHello2.exe
[Hello World]

参考ページ:
1.civic site >> ネイティブからJavaを呼び出す
2.JNI (Java Native Interface)
3.C/C++からJavaのライブラリを呼ぶ with JNI - #define NO_MONEY 0 4.2006/11/08 日記: Java: シンプルな C言語からJava言語を呼び出すJNIサンプル
Java Native Interface 仕様の目次

August 26, 2012

JNI その1:Javaからネイティブコードを呼び出す

Windows上で実施。
コンパイラはボーランドC++コンパイラを使用。
作業するフォルダは C:\work\Chap10\10-2 とする。
下記のフォルダ、ファイルのツリー表示で括弧で囲ってあるファイルはツール(javac、javah、Cコンパイラ)が出力するファイル。

作業フォルダ
│  jni-sample.c
│  [jni-sample.dll]
│  [jni-sample.obj]
│  [jni-sample.tds] … デバッグ用のシンボルファイル
│  [oesf_ace_JniCallToNativeFromJava.h]
│
└─oesf
    └─ace
            [JniCallToNativeFromJava.class]
            JniCallToNativeFromJava.java

  1. Javaのコードを作成
  2. JNIヘッダファイルを作成
  3. ネイティブコードを作成し共有ライブラリの作成
  4. 実行

Javaコードの作成

コードの場所はパッケージに合わせる。以下のコードの場合は.\oesf\ace に置く。

package oesf.ace;
public class JniCallToNativeFromJava {
    static {
        System.loadLibrary("jni-sample");
    }
    public native boolean isOne(int value);

    public static void main(String[] args) {
        JniCallToNativeFromJava jni = 
            new JniCallToNativeFromJava ();
        Boolean res = jni. isOne ( 1 );
        System.out.printf("res = " + res);
    }
}

作業フォルダでjavacコマンドを実行しコードをコンパイルする。

C:\work\Chap10\10-2>javac oesf\ace\JniCallToNativeFromJava.java

JNIヘッダの作成

作業フォルダでjavahコマンドを実行する。引数はファイル名ではなくクラスの完全修飾名。
命名規則に則った名前の関数の宣言がされたoesf_ace_JniCallToNativeFromJava.h というファイルが作業フォルダに作られる。

C:\work\Chap10\10-2>javah -jni oesf.ace.JniCallToNativeFromJava

命名規則は以下。

  • 接頭辞に「Java_」。
  • 完全修飾クラス名を分解し、それらとメソッド名をアンダースコアで連結する。

上記のJavaソースコードから命名規則に従うとネイティブメソッドの名前は

JNIEXPORT jboolean JNICALL Java_oesf_ace_JniCallToNativeFromJava_isOne(JNIEnv *, jobject, jint)

となる。

ネイティブコードを作成し共有ライブラリの作成

書籍に載っていたコードを少し修正。JNIヘッダがCソースファイルと同じフォルダにあるので、JNIヘッダを大なり小なりではなくダブルクウォーテーションで囲むように修正した。

#include <jni.h>
#include "oesf_ace_JniCallToNativeFromJava.h"
jboolean JNICALL 
Java_oesf_ace_JniCallToNativeFromJava_isOne
    (JNIEnv* env, jobject this, jint value) {
    if(value == 1) {
        return JNI_TRUE;
    }
    return JNI_FALSE;
}

これをコンパイルしてDLLを作成。
インクルードファイルのパスとして
<JDKのフォルダ>\include
<JDKのフォルダ>\include\win32
を追加する。
ボーランドC++コンパイラの場合はコンパイラのbinフォルダにある bcc32.cfg を修正するか、コマンドのオプションに追加する。
今回は bcc32.cfg を次のようにした。

-I"c:\Borland\Bcc55\include";"C:\progra~1\Java\jdk1.6.0_24\include";"C:\progra~1\Java\jdk1.6.0_24\include\win32"
-L"c:\Borland\Bcc55\lib"

DLLは以下のコマンドで作成。

C:\work\Chap10\10-2>bcc32 -c jni-sample.c
Borland C++ 5.5.1 for Win32 Copyright (c) 1993, 2000 Borland
jni-sample.c:
警告 W8057 jni-sample.c 10: パラメータ 'env' は一度も使用されない(関数 Java_oesf_ace_JniCallToNativeFromJava_isOne )
警告 W8057 jni-sample.c 10: パラメータ 'this' は一度も使用されない(関数 Java_oesf_ace_JniCallToNativeFromJava_isOne )

C:\work\Chap10\10-2>bcc32 -tWD -ejni-sample.dll jni-sample.obj
Borland C++ 5.5.1 for Win32 Copyright (c) 1993, 2000 Borland
Turbo Incremental Link 5.00 Copyright (c) 1997, 2000 Borland

実行

C:\work\Chap10\10-2>java oesf.ace.JniCallToNativeFromJava
res = true

参考文献:Androidアプリケーション開発標準資格教科書 10-2 Javaからネイティブコードの呼び出し

参考サイト:
Java Native Interface 仕様の目次
Borland C++ Compiler DLL作成
bcc32でDLLを作成する - 偏った言語信者の垂れ流し
Borland C++ Compiler Bcc32コマンドオプション

August 24, 2012

JUnit その5:カスタムルール

カスタムルール

org.junit.rules.TestRuleインタフェースを実装することで独自のルールを作成できる。
TestRuleインタフェースは次のメソッドが定義されている。

Statement apply(Statement base, Description description)
戻り値:
新しいStatement。それはbaseと同じもの、baseのラッパー、あるいは完全に新しいStatementかもしれない。

StatementはJUnitのテストを実行するオブジェクト。このクラスには evalute() が定義されている。
evalute() は次のような処理を実行する。

  1. テストクラスのインスタンスの生成
  2. @Beforeの付与されたメソッドの実行(初期化)
  3. テストメソッドの事項
  4. @Afterの付与されたメソッドの実行(後処理)

多くのカスタムルールのapply()はbase(オリジナルのStatement)の前後で独自の処理を実行するStatment、つまりプロキシオブジェクト(ドキュメントで言ってるところのラッパー)を返す。

Descriptionテストケースのメタ情報を保持する。

次のコードはアサーション失敗時のAssertionErrorを拡張するカスタムルール。
JUnitではテスト失敗時にjava.lang.AssertionErrorが送出され、フレームワーク層まで伝達されたエラー情報が表示される。
しかしパラメータ化されたテストではAssertionErrorniテストで使用したパラメータが含まれない。下記カスタムルールにパラメータ情報を追加すれば、テスト失敗のAssertionErrorにパラメータが含まれ、パラメータもエラー情報として表示されるようになる。

package junit.tutorial;

import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;

/**
 * アサーション失敗時の情報を拡張するカスタムルール
 */
public class AssertionMessage implements TestRule {
	
	private StringBuilder msg = new StringBuilder();
	
	public void append(String format, Object... params) {
		msg.append(String.format(format, params));
	}
	
	public String getMessage() {
		return msg.toString();
	}

	@Override
	public Statement apply(final Statement base, Description desc) {
		return new Statement() {
			
			@Override
			public void evaluate() throws Throwable {
				try {
					base.evaluate();
				} catch (AssertionError e) {
					AssertionError e2;
					if (msg.length() > 0) {
						msg.append('\n').append(e.getMessage());
						e2 = new AssertionError(msg.toString());
						e2.setStackTrace(e.getStackTrace());
					} else {
						e2 = e;
					}
					throw e2;
				}
			}
		};
	}
}

次のコードは上記のカスタムルールを適用したCalculatorクラスのテストクラス(乗算のみ)。

package junit.tutorial;

import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;

import org.junit.Rule;
import org.junit.experimental.runners.Enclosed;
import org.junit.experimental.theories.DataPoint;
import org.junit.experimental.theories.Theories;
import org.junit.experimental.theories.Theory;
import org.junit.runner.RunWith;

/**
 * AssertionMessageルールを適用したCalculatorのテストクラス
 */
@RunWith(Enclosed.class)
public class CalculatorRuledTest {

	@RunWith(Theories.class)
	public static class 乗算メソッドのパラメータ化テスト {
		
		@DataPoint public static Fixture DATA1 = new Fixture(3, 4, 12);
		@DataPoint public static Fixture DATA2 = new Fixture(5, 7, 12);
		
		@Rule
		public AssertionMessage message = new AssertionMessage();
		
		@Theory
		public void multiplyで乗算結果が取得できること(Fixture fx) {
			message.append("case: %d * %d = %d", fx.x, fx.y, fx.expected);
			System.out.println(message.getMessage());
			Calculator calc = new Calculator();
			int expected = fx.expected;
			int actual = calc.multiply(fx.x, fx.y);
			assertThat(actual, is(expected));
		}
		
		static class Fixture {
			int x, y, expected;

			Fixture(int x, int y, int expected) {
				this.x = x;
				this.y = y;
				this.expected = expected;
			}
		}
	}	
}

参考記事:WEB DB PRESS Vol.69 JUnit実践入門

参考サイト:
JUnit 4.9の@ClassRuleでクラス単位の前後処理 - 裏紙

August 19, 2012

JUnit その3:テストクラスの構造化とパラメータ化テスト

テストクラスの肥大化への対応としてテストクラスの構造化とパラメータ化テストがある。

テストクラスの構造化

org.junit.experimental.runners.Enclosedクラスはネストしたクラスをテストとして扱うことができるテストランナー。
テストをコンテキスト(初期状態、内部状態など)で分割し、それぞれのテストクラスをネストしたクラスにすればテストクラスを構造化できる。

パラメータ化テスト

パラメータ化テストに関数するクラス・アノテーションは org.junit.experimental.theories パッケージに定義されている。 Theoriesクラスはパラメータ化されたユニットテストをサポートするテストランナー。
テストメソッドには @Test ではなく @Theoryを付ける。これでテストメソッドに引数を取らせることができる。
パラメータ(テストデータ)は @DataPoint または @DataPoints を使用する。@DataPoint ではテストデータをpublic staticなフィールドまたはメソッドで1つずつ定義する。たとえば

@DataPoint
public static int INT_PARAM_1 = 3;
@DataPoint
public static int INT_PARAM_2 = 4;

とパラメータを定義した場合、

テストメソッド(3, 3)
テストメソッド(3, 4)
テストメソッド(4, 3)
テストメソッド(4, 4)

のようにパラメータの順列の数だけすべてのパターンでテストが行われる。各パターンごとにインスタンスが生成されるので、初期かも各パターンごとに行われる。

@DataPointsではテストデータを配列としてまとめて定義する。
サンプルコードでは割られる数、割る数、期待値の3つのパラメータが必要だがすべての順列のパターンでテストが行われるのは問題なので、引数をオブジェクトにしてオブジェクトの配列をパラメータとして定義している。

@RunWith(Enclosed.class)
public class CalculatorParameterizedTest {

	@RunWith(Theories.class)
	public static class 乗算メソッドのパラメータ化テスト {
		
		@DataPoint public static Fixture DATA1 = new Fixture(3, 4, 12);
		@DataPoint public static Fixture DATA2 = new Fixture(5, 7, 35);
		
		@Theory
		public void multiplyで乗算結果が取得できること(Fixture fx) {
			
			String msg = fx.x + "*" + fx.y + "=" + fx.expected;
			System.out.println(msg);
			Calculator calc = new Calculator();
			int expected = fx.expected;
			int actual = calc.multiply(fx.x, fx.y);
			assertThat(msg, actual, is(expected));
		}
		
		static class Fixture {
			int x, y, expected;

			Fixture(int x, int y, int expected) {
				this.x = x;
				this.y = y;
				this.expected = expected;
			}
		}
	}
}

参考記事:WEB DB PRESS Vol.69 JUnit実践入門

August 18, 2012

JUnit その2:aseertThat()とMatcher API

アサーション

アサーションとは比較検証の仕組みの事。
JUnit4.4以降では assertThat() と Matcher API を使用する。
以下のような特徴がある。

  • より自然言語(英語)に近い記述
  • 比較方法(Matcher)と検証フレームワーク(asertThat())の分離
  • より詳細なエラー情報

Matcher

Matcher は自然言語に近い記述をするためにstaticインポートして使う。
Matcher は org.hamcrest.CoreMacther#is() のようなstaticなファクトリメソッドを介して使用する。is()以外に次のようなさまざまなファクトリメソッドがある。

  • CoreMatcher#nulValue()
    実測値がnullであるかを評価する。
  • CoreMatcher#not()
    他の Matcher の評価値を反転する。
  • org.junit.matchers.JUnitMatcher#hasItem()
    リストや配列などIterableインタフェースを実装したクラスのインスタンスの実測値に期待する値が含まれているかを評価する。

カスタムMatcher

独自のMatcherを作ることができる。org.hamcrest.BaseMatcher を継承して作成するとよい。
BaseMatcher は org.hamcrest.Matcherインタフェースを実装しており、カスタムMatcherはファクトリメソッドとMatcherインタフェースのmatches()、describeTo() を実装する。

boolean matches(Object item)
評価するメソッド
void describeTo(Description description)
評価の失敗時に理由を通知するためのメソッド

DescriptionクラスのappendValue()で追加された値はダブルクウォーテーションで囲まれて表示される。appendText()で追加された値はダブルクウォーテーションで囲まれずそのまま表示される。

下記はDateの日付(年/月/日)だけを比較するカスタムMatcher。

package junit.tutorial;

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;

import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.hamcrest.Matcher;

/**
 * カスタムMatcher。Dateの日付(年/月/日)だけを比較する
 */
public class IsDate extends BaseMatcher<Date> {

	private final int year;
	private final int month;
	private final int date;
	
	Object actual;
	
	IsDate(int year, int month, int date) {
		this.year = year;
		this.month = month;
		this.date = date;
	}
	
	@Override
	public boolean matches(Object actual) {
		
		this.actual = actual;
		if (!(actual instanceof Date)) {
			return false;
		}
		
		Calendar cal = Calendar.getInstance();
		cal.setTime((Date)actual);
		if (year != cal.get(Calendar.YEAR)) {
			return false;
		}
		if (month != cal.get(Calendar.MONTH) + 1) {
			return false;
		}
		if (date != cal.get(Calendar.DATE)) {
			return false;
		}
		return true;
	}

	@Override
	public void describeTo(Description desc) {
		desc.appendValue(year + "/" + month + "/" + date);
		if (actual != null) {
			desc.appendText(" but actual is ");
			desc.appendValue(new SimpleDateFormat("yyyy/M/d").format((Date)actual));
		}
	}

	public static Matcher<Date> dateOf(int year, int month, int date) {
		return new IsDate(year, month, date);
	}
}

参考記事:WEB DB PRESS Vol.69 JUnit実践入門

August 16, 2012

JUnit その1

JUnit3とJUnit4

現時点での最新版は2011年10月リリースの4.10。
JUnit3もまだかなり使われているようだ。「Androidアプリケーション開発標準資格教科書」にはAndroidのテストフレームワークはJUnit3をベースにしていると書いてある。
JUnit3とJUnit4の相違点はかなり大きい。以下はJUnit4を前提とした内容。

Quick JUnit

Quick JUnitはEclipseのプラグイン。
Ctrl+9 でテスト対象クラスとテストクラスの切り替え、Ctrl+0 でテストの実行ができる。
Eclipseマーケットプレイスからインストールできる。

プロジェクトの準備

JUnitの最新版を使いたい場合はプロジェクトのフォルダにJarファイルを追加し、そのJarファイルのコンテキストメニューで[ビルド・パス]-[ビルド・パスに追加]とする。 Eclipseに付属のJUnitを使う場合は、テストクラスやテストスイートをウィザードで作成するときに自動的にビルドパスに追加される、と思う。
自分が使っているEclipse Indigoには3.8.2、4.8.1、4.8.2があるようだ。

テストコード用のソースフォルダを作る。プロジェクトのコンテキストメニューの[ビルド・パス]-[新規ソースフォルダー、または[新規]-[その他]の[ソースフォルダー]でソースフォルダtestを作成する。
テストクラスはテスト対象と同じパッケージにするあが、置く場所は異なるフォルダにするのがいいらしい。

テストクラスの作成

テスト対象クラスのコンテキストメニューで[新規]-[その他]から[JUnitテスト・ケース]または[JUnitテスト・スイート]を選択してウィザードを使用してテストクラスを作成できる。
また、Quick JUnitではインストール済みの場合はテスト対象クラスをエディタで開きCtrl+9でウィザードが起動する(テストクラスがある場合はテストクラスに切り替わる)。

JUnitのルール

JUnitのテストコードには以下のルールがある。

  • テストクラスはpublicクラス。
  • テストメソッドはorg.junit.Testアノテーションを付与したpublicメソッド。
  • テストメソッドは戻り値がvoidで引数を持たない。
  • パラメータ化テストメソッドはorg.junit.experimental.theories.Theorアノテーションを付与したpublicメソッド
  • パラメータ化テストメソッドは戻り値がvoidで引数を取る。

実測値と期待値の比較にはスタティックメソッド org.junit.Assert#assertThat() を使用する。

public static <T> void assertThat(T actual, org.hamcrest.Matcher<T> matcher)
public static <T> void assertThat(String reason, T actual, org.hamcrest.Matcher<T> matcher)
引数
  reason - エラーについての追加情報
  actual - 実測値(actual value)
  matcher - 期待値との比較を行うMatcherオブジェクト

Matcherオブジェクトはファクトリメソッドで作成する。サンプルコードではis()がファクトリメソッド。

例外が発生することを期待するテストではTestアノテーションのexpected属性に発生する例外クラスのclassをセットする。

テストの実行

テストクラスのコンテキストメニューで[実行]-[JUnitテスト]を選択して実行する。
Quick JUnitではテストメソッド内にカーソルを置いてCtrl+0で実行する。

その他、気付いたこと

JUnitではアノテーションを利用しているが、アノテーションは型なのでimportしないと使えない。
#標準アノテーション(Overrideとか)はimportしなくてもいいみたいだが。java.langパッケージにあるからか?
Version#id()はJUnitのバージョンを返す。

System.out.println("junit version: " + junit.runner.Version.id());

下記は計算機クラス(掛け算・割り算)とそのテストクラス。
参考記事に従ってテストメソッド名を日本語にしてある。わかりやすいが違和感を覚える人もいるかも。

テスト対象クラス
package junit.tutorial;

/**
 * 計算機クラス
 */
public class Calculator {

	public int multiply(int x, int y) {
		return x * y;
	}

	public float divide(int x, int y) {
		if (y == 0) {
			throw new IllegalArgumentException();
		}
		return (float)x /(float)y;
	}
}
テストクラス
package junit.tutorial;

import static org.junit.Assert.*;
import static org.hamcrest.CoreMatchers.*;
import static junit.tutorial.IsDate.*;

import java.util.Date;

import org.junit.Test;

/**
 * 計算機クラスのテストクラス
 */
public class CalculatorTest {

	@Test
	public void multiplyで3と4の乗算結果が取得できる() {
		Calculator calc = new Calculator();
		int expected = 12;
		int actual = calc.multiply(3, 4);
		assertThat(actual, is(expected));
	}

	@Test
	public void multiplyで5と7の乗算結果が取得できる() {
		Calculator calc = new Calculator();
		int expected = 35;	// 35が正しい期待値
		int actual = calc.multiply(5, 7);
		assertThat(actual, is(expected));
	}

	@Test
	public void divideで3と2の除算結果が取得できる() {
		Calculator calc = new Calculator();
		float expected = 1.5f;
		float actual = calc.divide(3, 2);
		assertThat(actual, is(expected));
	}

	@Test(expected = IllegalArgumentException.class)
	public void divideの第2引数に0を指定した場合にはIllegalArgumentExceptionを送出する() {
		Calculator calc = new Calculator();
		calc.divide(5, 0);
	}
	
	@Test
	public void 第3章のカスタムMatcherの確認() {
		assertThat(new Date(), is(dateOf(2012, 1, 12)));
	}
}

参考記事:WEB DB PRESS Vol.69 JUnit実践入門

参考サイト:
JUnit4 - TRANCE ARTS 技術情報Wiki
「テスト駆動開発入門」を JUnit 4.4 と Eclipse 3.3 でやってみる (1)
JUnit4をやってみよう
JUnitメモ(Hishidama's JUnit Memo)

May 09, 2012

MySQLメモ013:INSERT後にAUTO_INCREMENTなカラムの値を取得する方法

Java+MySQLなWEBアプリにおいて、AUTO_INCREMENTなカラムを持つテーブルにINSERTでレコードを挿入してから、その新規レコードのAUTO_INCREMENTなカラムの値を取得する方法について、ちょっとてこずったのでメモ。 環境は以下。
Java:1.6.0_31
MySQL:5.5.19

本やネットで調べると LAST_INSERT_ID() というMySQLの関数を使う方法もあるようだが、ここはせっかくなのでMySQLのJDBCドライバ(MySQL Connector/J)が提供するAPIを使ってみた。

サンプルコード
final String sql = "INSERT INTO orders (customer_name, address, payment) VALUES (?, ?, ?)";

DataSource dataSource = (DataSource)context.lookup("java:comp/env/jdbc/mysql");
Connection conn = dataSource.getConnection();
PreparedStatement statement = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
statement.setString(1, customerName);
statement.setString(2, address);
statement.setInt(3, payment);
statement.executeUpdate();

// 上記の処理で登録したデータのAUTO_INCREMENTで生成されたIDを取得する
PreparedStatement stmt2
    = (PreparedStatement)((DelegatingPreparedStatement) statement)
        .getInnermostDelegate();
ResultSet rs = ((com.mysql.jdbc.PreparedStatement)stmt2).getGeneratedKeys();
if (rs.next()) {
	id = rs.getInt(1);
} else {
	throw new SQLException("failure: retrieve new id");
}

上記のコードのWEBアプリでは、JNDIで DataSource を取得し DataSource.getConnection() で java.sql.Connection を取得して変数connに入れている。
そのconnで作成した PreparedStaement を作成し statement に代入(5行目)する。ここで使用する prepareStatement() は引数にSQLとフラグを取るもので、フラグには Statement.RETURN_GENERATED_KEYS をセットする。

java.sql 
インタフェース Connection
PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException
自動生成キーを取得する機能を持つデフォルトの PreparedStatement オブジェクトを生成します。
パラメータ:
sql - 1 つ以上の '?' IN パラメータプレースホルダーを含めることができる SQL 文
autoGeneratedKeys - 自動生成キーを返すかどうかを示すフラグ。Statement.RETURN_GENERATED_KEYS または Statement.NO_GENERATED_KEYS 
戻り値:
プリコンパイルされた SQL 文を含む新しい PreparedStatement オブジェクト。自動生成キーを返す機能を持つ 
例外: 
SQLException - データベースアクセスエラーが発生した場合、このメソッドがクローズされた接続に対して呼び出された場合、または指定されたパラメータが自動生成キーを返すかどうかを示す Statement 定数でない場合 
SQLFeatureNotSupportedException - JDBC ドライバが定数 Statement.RETURN_GENERATED_KEYS を指定したこのメソッドをサポートしない場合

作成した PreparedStaement は com.mysql.jdbc.PreparedStatement ではないらしい。statement を com.mysql.jdbc.PreparedStatement でキャストしようとすると例外が発生する。
そこで、 statement を org.apache.tomcat.dbcp.dbcp.DelegatingPreparedStatement にキャストし、getInnermostDelegate() で内部に保持されていた PreparedStatement を取得し stmt2 に代入する(12-15行目)。この取得した PreparedStatement は com.mysql.jdbc.PreparedStatement らしいので getGeneratedKeys() で ResultSet を取得し AUTO_INCREMENT なカラムの値を取得する(15,17行目)。

org.apache.commons.dbcp 
クラス DelegatingPreparedStatement
public PreparedStatement getInnermostDelegate()
内部に保持する PreparedStatement が DelegatingPreparedStatement でない場合にはその PreparedStatement を返し、それ以外の場合には再帰的に getDelegate() をコールします。 
従ってこのメソッドは DelegatingPreparedStatement ではない根本の処理の委託先となる PreparedStatement を返し、 DelegatingPreparedStatement の連鎖の中に処理の委託先が見つからない場合には null を返します。 
このメソッドはネストした DelegatingPreparedStatement から 本来の PreparedStatement を取得したい場合に有用です。 

JNDIを使用せず下記のように Connection を取得した場合は DelegatingPreparedStatement#getInnermostDelegate() は不要で、直接キャストして getGeneratedKeys() を使えばよい。

Connection conn = DriverManager.getConnection("jdbc:mysql://localhost/java_sample_db", "java", "password");

参考ページ:MySQL :: MySQL 5.1 リファレンスマニュアル :: 24.4.5.1 JDBC の基本コンセプト

より以前の記事一覧

March 2017
Sun Mon Tue Wed Thu Fri Sat
      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  
無料ブログはココログ

日本blog村

  • にほんブログ村 IT技術ブログへ
  • にほんブログ村 アニメブログへ
  • にほんブログ村 サッカーブログ アルビレックス新潟へ

好きな音楽家

メモ

XI-Prof