Bits of Java (トップ)

VM   JDK と JRE
Language   オブジェクトとインスタンス   this と super   static   フィールドアクセス
IO   Serializable の実装
Swing   MetalLookAndFeel   イメージパネル
JavaBeans   プロパティ名について   XMLEncoder で保存
その他   正規表現テストアプリ   秀丸の強調表示   Ant のインストール   文字セット変換 Ant タスク   XAMPP + Tomcat

Java 正規表現テストアプリケーション

Java 1.5 以降の正規表現をテストするアプリケーションの紹介です。 作成した正規表現パターンが想定通りのマッチングを行うかどうかを視覚的にテストできます。 また Java は使えるが正規表現は使ったことがないという方には正規表現の学習・入門用としても使えると思います。 BSD ライセンスで公開しています。

インストール
アプリケーションについて
不明な点やあいまいな点など
見た目やソースの文字エンコーディングの変更など


インストール

インストールはこちらから regextester.zip をダウンロードし、お使いの解凍ツールや JDK に含まれている jar コマンドで任意のディレクトリに解凍するだけです。 jar で解凍する場合のコマンドは以下です。
jar xf regextester.zip
解凍するとregextester ディレクトリが作成されその中にソースと Ant のビルドファイル、ライセンスと共に regextester.jar があります。

アプリケーションの実行に必要なのは regextester.jar だけなので必要なければそれ以外のファイルはすべて破棄してくださってかまいません。 Windows XP の場合には JDK 1.5 以上をデフォルトの設定の通りにインストールしている場合には 拡張子 jar が JRE_HOME/bin/javaw.exe に関連づけられているはずなので、 regextester.jar をダブルクリックなどで実行するだけでアプリケーションが起動すると思います。 拡張子 jar が関連づけされていない場合や解凍ツールなどの別のアプリケーションに関連づけられている場合にはコマンドプロンプトやターミナルなどで regextester.jar のあるディレクトリに移動して以下のコマンドで起動してください。
java -jar regextester.jar

ソースから regextester.jar を作成する場合にはビルドツール Ant が必要です(Ant のインストール方法)。 ビルドはコマンドプロンプトやターミナルなどで regextester ディレクトリに移動して以下のコマンドを実行するだけです。
ant
src ディレクトリ以下をコンパイルして生成したクラスファイルを XML ファイルやイメージファイルなどのリソースと共に regextester.jar にパックして regextester ディレクトリに出力します。


アプリケーションについて

下はアプリケーションのイメージ(MetalLookAndFeel Ocean)です。

左のタブは Pattern クラス、 Matcher クラスのメソッドやメタ文字、その他正規表現に関する説明を表示する為のコンポーネントが格納されています。 それらの説明は中央一番下のテキストエリアに表示されます。 説明内容は Java 1.5 の正規表現実装に依存しており、 Perl や C# など他の正規表現エンジンとの動作の違いに関してなどは触れていません。

マッチングを行う場合は 中央上の "正規表現:" というラベルの右のテキストフィールドに正規表現パターンを入力し、その下のテキストエリアにマッチングの対象の文字列を入力して(あるいは [Ctrl]+V によってコピーした文字列を貼り付けて) find ボタンを押します。 マッチした部分があれば上のイメージのように明るい背景色を使って強調表示します。 マッチしたのが位置の場合にはその位置の上部に小さな三角形を表示します。 半角スペース、全角スペース、タブ、改行(LF または CRLF)は図形で表示します。 CR を改行文字とする文章には対応していません。 Java コードの文字リテラルではバックスラッシュ \ (日本語フォントでは円記号にマッピング)はエスケープシーケンスを使って \\ と記述しなければなりませんが、 "正規表現:" ラベルの右のテキストフィールドではエスケープシーケンスを使う必要はありません。

正規表現を使った事がないという方はこのページのこれ以降の部分はほとんど興味の無い内容だと思われます。アプリケーションを起動したら、左側のタブから "その他" を選んで一読し、次に "メタ文字" を開いて、それぞれのメタ文字の意味を理解し、実際にそのメタ文字を含む正規表現パターンを作成してマッチングを行ってみる事をお勧めします。

マッチングの結果は右側上部の "状態" にも表示します。 入力文字数はマッチングの対象文字列の文字数で、 groupcount はキャプチャグループの数、 requiredEnd hitEnd は Matcher クラスの同名メソッドの返す値を表示します。 "group" ラベルの右のコンボボックスはキャプチャグループの番号を選択します。 0 は常にマッチした文字列全体を指し、 1 以上の値は正規表現にキャプチャグループが使われている場合にのみ選択可能で、選択したキャプチャグループにマッチした部分を強調表示します。 start や end は強調表示している部分の開始と終了位置を示し、これらは Matcher クラスの同名メソッドの返す値です。

マッチングの際のモードの設定は右側中央の "モード" で行います。 チェックボックスにチェックの入っているモードが有効になります。

右側下の "領域と境界" はマッチングの対象となる文字列への領域の設定や領域境界の設定を行います。 テキストフィールドに設定する領域の開始位置と終了位置を入力して region ボタンを押すと領域を設定し、設定した値を "regionstart:" "regionend:" ラベルの右に表示します。 anchoringBounds や transparentBounds はマッチングの際の領域境界における正規表現エンジンの動作を設定します。 領域の設定はバージョン 1.5 から導入されたもので、この領域や領域境界の設定に関しても左タブの "その他" を開くと説明へのリンクがあります。 このアプリケーションでは JEditorPane を使って HTML を表示してますが、そこで使われているハイパーリンクはネットに接続するわけでは無く、ボタンの代わりのイベントの発生源として使用されています。

中央上部の lookingAt matches find find(int) などのマッチングを行うボタンは Matcher クラスの同名のメソッドを実行します。 一度マッチングを行った後に正規表現パターンやマッチングの対象となる文字列を編集する場合には reset または クリアボタンを押す必要があります。 Matcher クラスのメソッドに関しては左タブで Matcher を開きメソッド名をクリックするとその説明が表示されます。

中央中程の "置換構文:" ラベルの右のテキストフィールドには下の appendReplacement replaceFirst replaceAll ボタンで置換を行う際の置換文字列を入力します。 appendReplacement replaceFirst replaceAll appendTail などのボタンは入力されている正規表現パターンやマッチング対象の文字列、置換文字列を使って Matcher クラスの同名のメソッドを実行し、その結果を中央のテキストエリアに表示します。 "置換文字列" ではなく、 "置換構文" としたのは $ がキャプチャグループを参照する特別な意味を持つ事もあるのですが、本当のところは "正規表現:" ラベルと文字数を合わせる為なのであまり気にしないでください。 アプリケーションの中の説明では置換文字列の方を使っています。 置換文字列を入力するテキストフィールドには常にダブルクォート "" が表示されていますが、このダブルクォートは置換文字列には含まれず、入力内容が Java プログラムにおける文字リテラルと同じように記述されなければならない事を明確にする為に表示しています。 つまり、バックスラッシュ \ は \\ と入力しなければなりません。

中央一番下の "前へ" "次へ" ボタンは1つの説明から別の説明にリンクで移動した場合などに元に戻ったり、先に進んだりする為にあり、 "チュートリアルモードクリア" ボタンはチュートリアルモードを解除します。 チュートリアルモードとは説明の際に具体例を実行するモードで前出のアプリケーションのイメージはキャプチャグループを置換構文から参照する例を表示している画面です。 チュートリアルモードではフレームのボタンを除いて "前へ" "次へ" "チュートリアルモードクリア" ボタンと説明部分のリンクをクリックする以外のマウス操作は無視されます。 よってチュートリアルモードを解除するには "前へ" "次へ" "チュートリアルモードクリア" ボタンのどれかをクリックする必要があります。


不明な点やあいまいな点など

このアプリケーションを作っている際によく分からなかった点、腑に落ちない点などはアプリケーションでは特に触れずに流しているので、ここに記述しておきたいと思います。

まず 1.5 で導入された hitEnd メソッドと requireEnd メソッドですが、 hitEnd メソッドはドキュメントでは入力の末尾がヒットした場合にtrueを返すとありますが、入力というのは領域を指しているようなのですが、末尾というのが最後の文字なのか末尾の位置なのかよく分かりませんでした。 例えば aa をマッチング対象の入力シーケンスとして正規表現 aa でマッチングを行った場合、入力シーケンス全体にマッチしますが、この場合に hitEnd は false を返します。 つまり最後の文字がマッチしていても true にはならないようです。 ところが正規表現 a+ や a* でマッチングを行うと aa と同様に入力シーケンス全体にマッチしますが、この場合は hitEnd は true となります。 つまりマッチした部分だけでなく正規表現パターン自体も hitEnd の返す値に影響するようです。
また正規表現 $ は入力シーケンスの末尾が行末記号で終わっている場合にはその行末記号の前後にマッチしますが、その前後どちらにマッチした場合でも hitEnd requireEnd はともに true を返します。 例えば改行文字 LF で入力シーケンスが終わっている場合に $ が LF の直後の位置にマッチした際に hitEnd や requireEnd が true を返すのは分かりますが、LF の直前の位置に $ がマッチしている場合にも true を返すのはおかしいような気がします。
hitEnd メソッド、requireEnd メソッド共にあまり使用する機会はないと思いますがプログラムで使う場合には想定通りに動くかテストした方がよいと思われます。

次に戻り読みについてですが、このアプリケーションでは前方一致指定 (?<= ) や前方不一致指定 (?<! ) と呼んでいます。 詳説 正規表現 第2版(書籍 オライリージャパン刊)に記述されていたのですが Java の正規表現エンジンでは戻り読みの部分で上限を指定しない数量子 + * {m,} は使えないそうです。 Java 1.5 でテストしてみた結果は、たまたま正常に動作する場合と PatternSyntaxException が発生する場合と例外はスローされないが正しく動作しないという3つの動作に分かれました。 この中で例外はスローされないが正しく動作しないというケースは最悪で間違った結果を堂々と返す事になります。 API ドキュメントに記述はありませんが戻り読みを行う場合には上限の無い数量子は使わない方がよいでしょう。
また戻り読みの際にはバックトラックは行われないそうです(戻り読みの場合のアルゴリズムが 分からないのでもしかするとバックトラックという表現をここで使うのはまちがいかもしれませんが)。 例えば ab は正規表現 (?<=a|ab)b に  b がマッチしますが (?<=ab|a)b にはマッチしません。
また  aa は正規表現 (?<=a{1,2}?)a に2番目の a がマッチしますが (?<=a{1,2})a にはマッチしません。

最後は CANON_EQ モードについてです。 API ドキュメントには以下のように記述されています。

このフラグを指定したときは、2 つの文字の完全な標準分解がマッチした場合に限り、それらの文字がマッチするとみなされます。
実際には Sun 実装の Java 1.5 における動作では CANON_EQ モードでは正規表現パターンとなる文字の正規分解 (NFD) または 正規合成 (NFC) とマッチング対象の文字が同じ場合にマッチするようです。 例えばひらがなの  (U+3071) は CANON_EQ モードでは NFD である文字 "\u306F\u309A" (\u306F →   \u309A → 対応する文字はないが半濁点 ゜を表すコード)にマッチします。
Pattern pattern = Pattern.compile("ぱ", Pattern.CANON_EQ);
Matcher matcher = pattern.matcher("\u306F\u309A");
System.out.println(matcher.matches());
このコードを実行すると true が表示されます。

ドキュメントの通りなら(ラテン文字の上リング付き大文字 A → U+00C5)と (オングストローム → U+212B)は普通はマッチしませんが正規分解すると両方とも A (U+0041) と上リング(U+030A)に分解されるので CANON_EQ モードではこの2つの文字はマッチする事になりますが、実際には2つの文字がマッチするという表現は正しくありません。
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Test{
    public static void main(String[] args) {
        //上リング付き大文字A 00C5
        //オングストローム   212B
        Pattern pattern = Pattern.compile("\u212B", Pattern.CANON_EQ);
        Matcher matcher = pattern.matcher("\u00C5");
        System.out.println(matcher.matches());

        Pattern pattern2 = Pattern.compile("\u00C5", Pattern.CANON_EQ);
        Matcher matcher2 = pattern2.matcher("\u212B");
        System.out.println(matcher2.matches());

        Pattern pattern3 = Pattern.compile("\u212B", Pattern.CANON_EQ);
        Matcher matcher3 = pattern3.matcher("\u212B");
        System.out.println(matcher3.matches());
    }
}
上のプログラムを実行すると以下が出力されます。
true
false
false
上のプログラムの場合には

Pattern pattern = Pattern.compile("\u212B", Pattern.CANON_EQ);
Pattern pattern = Pattern.compile("\u00C5", Pattern.CANON_EQ);
CANON_EQ モード で作成されたこの2つのパターンは両方ともオブジェクト内部では (?:\u0041\u030A|\u00C5) となります。 よってどちらのパターンも正規分解 (NFD) の "\u0041\u030A" (Aと上リング)か正規合成 (NFC) の "\u00C5" (上リング付き大文字A)のどちらかにマッチする事になります。 つまり CANON_EQ モードではパターンを "\u212B" で作成した場合でも、もとの文字 "\u212B" にはマッチしなくなります。

また、これもよく分からない動作なのですが CANON_EQ モードでパターンを生成する際に
Pattern pattern = Pattern.compile("ダヂヅデド", Pattern.CANON_EQ);
このように標準分解される文字が2つ以上続くと java.util.regex.PatternSyntaxException が発生します。(Sun 実装 Java1.5 における動作)


アプリケーションの見た目やソースの文字エンコーディングの変更

アプリケーションを起動してみて、ルックアンドフィールや色などが気に入らない場合にはプロパティファイルで変更可能です。 特に右下にある程度のスペースができるとイメージを表示するようになっており、デフォルトでは鉛筆を持った Duke を表示しますが、これが拡大されて見苦しい場合には別のイメージと置き換えてください。 イメージの変更は、例えば another.gif というイメージファイルに置き換える場合は src/jp/gr/java_conf/boj/app/regex ディレクトリに another.gif を配置して Shift_JIS を使う場合にはregex ディレクトリにある regextester_ja.sjis を EUC-JP を使う場合には regextester_ja.eucjp をテキストエディタで開いてファイルの末尾の方にある以下の部分を編集します。

########## TrademarkPanel ##########
trademarkpanel.string.imagefile=penduke.gif
次のようにします。
########## TrademarkPanel ##########
#trademarkpanel.string.imagefile=penduke.gif
trademarkpanel.string.imagefile=another.gif
編集後に regextester ディレクトリで regextester_ja.sjis を編集した場合には
ant create_sjis
を実行し、 regextester_ja.eucjp を編集した場合には
ant create_eucjp
を実行します。 この ant の create_sjis または create_eucjp ターゲットの実行で regextester.jar の内容が更新されて、右下にスペースが出来ると another.gif を表示するようになります。 ただし、右下に表示するイメージはスペースに合わせて拡大されますが、縮小表示はしないので、右下にできるスペースよりも大きな画像を設定した場合には表示されません。

ルックアンドフィールや色の設定なども変更できます。 右下のイメージの変更と同様に regextester_ja.sjis または regextester_ja.eucjp を編集して ant create_sjis または ant create_eucjp で regextester.jar を更新します。 インストール時の設定に戻す場合には以下のコマンドを実行します。
ant defaultproperties
ルックアンドフィールが com.sun.java.swing.plaf.windows.WindowsLookAndFeel の場合には JComboBox に正しくテキストが表示されませんがこれは 報告されているバグ で Closed, fixed になっていますがいまだに発生します。

Java ソースコードは文字エンコーディングに windows-31j (MS932) を使い改行文字は CRLF ですが 互換性のない文字は使用していないと思いますので Shift_JIS としても問題ないはずです。 文字エンコーディングや改行文字を変更する Ant タスクをインストールしてくださっている場合には簡単に文字エンコーディングや改行文字を変更しながらディレクトリごとコピーできます。
forunix ディレクトリを作成し、その中に src ディレクトリを文字エンコーディングを EUC-JP へ、すべてのテキストの改行文字を LF に変更しながらコピーする場合には以下のコマンドを実行します。(EUC-JP への変換は Java ソースのみで XML ファイルは UTF-8 のままです)
ant forunix

formac ディレクトリを作成し、その中に src ディレクトリをすべてのテキストの改行文字を LF にしながらコピーする場合には以下のコマンドを実行します。
ant formac

alleucjp ディレクトリを作成し、その中に src ディレクトリをすべてのテキストの文字エンコーディングを EUC-JP に改行文字を LF に変更しながらコピーする場合には以下のコマンドを実行します。(XML ファイルも EUC-JP に変換します)
ant alleucjp

allsjis ディレクトリを作成し、その中に src ディレクトリをすべてのテキストの文字エンコーディングを windows-31j に改行文字を LF に変更しながらコピーする場合には以下のコマンドを実行します。(XML ファイルも windows-31j に変換します)
ant allsjis

なお alleucjp と allsjis ターゲットで作成した src ディレクトリ内のソースをコンパイルしても XML が UTF-8 から変更されている為に実行はできません。