読者です 読者をやめる 読者になる 読者になる

M12i.

学術書・マンガ・アニメ・映画の消費活動とプログラミングについて

Apache Ant の DirectoryScannerの動作を見てみる

前回の投稿でも紹介したとおり、DirectoryScanner (org.apache.tools.ant. DirectoryScanner)はApache Antが提供しているAPIです。このAPIを使用することで、ワイルドカードを含むパターンを使用したファイル/ディレクトリの検索が可能になります。今回は、同APIに種々のパラメータを指定した場合の挙動について見てみます。

API基本的な使用方法

DirectoryScannerクラスは、org,apache.tools.antパッケージに属しています。このパッケージは、Apache Antプロジェクトが配布しているアーカイブ(例えばapache-ant-1.9.3-bin.zip)に格納されているant.jarに含まれています。

DirectoryScannerを使用する場合はまずこのant.jarを入手して、Javaプロジェクトのビルド・パスに登録します。

そのうえで、以下のようにクライアント・コードを記述します。

// DirectoryScannerを初期化する
final DirectoryScanner ds = new DirectoryScanner();

// 包含パターンを設定する
ds.setIncludes(new String[]{"**/foo/**", "**/bar/**"});

// 除外パターンを設定する
ds.setExcludes(new String[]{"**/*.class"});

// ベース・ディレクトリを設定する
ds.setBasedir(new File("c:\dirscan"));

// ディレクトリ・ツリーを読み取る
ds.scan();

// 条件に適合したファイル・パスの一覧を配列として取得
final String[] files = ds.getIncludedFiles();

後ほど検証作業のなかでも言及しますが、ベース・ディレクトリの設定は重要です。

包含/除外パターンは、このベース・ディレクトリからの相対パスとして機能します。アプリの使用者が設定した任意のパターンに基づいてファイル/ディレクトリを検索するようなロジックを実装する場合、ベース・ディレクトリについての考慮が漏れると、「どうしても検索できないファイル/ディレクトリがある」とか、反対に「検索対象となるべきでないファイル/ディレクトリが対象となってしまう」といった問題が発生します。

また、実装を確認していないので正確なところはわかりませんが、公式ドキュメントによれば、DirectoryScannerはパターン・マッチに先立ち、ベース・ディレクトリとして指定されたディレクトリ配下のファイル/ディレクトリを再帰的に全スキャンする("A given directory is recursively scanned for all files and directories.")ということです。したがってベース・ディレクトリにルートを設定すると、包含/除外パターンにもよると思いますが、スキャンとパターン・マッチの処理による負荷が相当量発生する可能性があるようです。

ともあれ、基本的な使用方法をおさえたところで、検証作業をしてみましょう。

検証用のファイルを用意する

まずDirectoryScannerで読み取る対象となる、検証用のディレクトリ/ファイルを用意します。

f:id:m12i:20140119015135p:plain

以下のような構成でディレクトリ/ファイルを用意しました。

c:
├...
└dirscan
  ├foo1
  │├foo1_1
  ││├foo1_1.class
  ││└foo1_1.java
  │├foo1_2
  ││├foo1_2.class
  ││└foo1_2.java
  │├foo1.class
  │└foo1.java
  ├foo2
  │├foo2_1
  ││├foo2_1.class
  ││└foo2_1.java
  │├foo2_2
  ││├foo2_2.class
  ││└foo2_2.java
  │├foo2.class
  │└foo2.java
  └bar1
    ├bar1_1
    │├bar1_1.class
    │└bar1_1.java
    ├bar1_2
    │├bar1_2.class
    │└bar1_2.java
    ├bar1.class
    └bar1.java

検証用のプロジェクトを作成

続いて検証用のプロジェクトを作成します。ビルドパスにはもちろん ant.jar を追加します。

f:id:m12i:20140119015644p:plain

検証用のソースコードは以下の通り。

はじめのメソッドは、DirectoryScannerを初期化するためのものです。2つ目はDirectoryScannerを使って実際にディレクトリ・ツリーを読み取るためのもの。最後に、mainメソッドで前述の2つのメソッドを使用して検証を行っています。

package com.m12i.dirscan;

import java.io.File;

import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.DirectoryScanner;
import org.apache.tools.ant.types.selectors.FileSelector;

public class Main {
	
	public static DirectoryScanner makeScanner(String baseDir, String[] includes, String[] excludes) {
		final DirectoryScanner ds = new DirectoryScanner();
		ds.setIncludes(includes);
		ds.setExcludes(excludes);
		ds.setBasedir(new File(baseDir));
		ds.setCaseSensitive(true);
		return ds;
	}
	
	public static void scanAndPrint(String label, DirectoryScanner ds) {
		ds.scan();
		System.out.println(label + ":");
		String[] files = ds.getIncludedFiles();
		for (int i = 0; i < files.length; i++) {
			System.out.println(files[i]);
		}
		System.out.println();
	}
	
	public static void main(String[] args) {
		// example 1
		final DirectoryScanner ds1 = makeScanner(
				"c:\\dirscan",
				new String[]{"**\\*.class"},
				new String[]{"bar*\\**"});
		
		scanAndPrint("Example 1", ds1);
		
		// example 2
		final DirectoryScanner ds2 = makeScanner(
				"c:\\dirscan",
				new String[]{"**\\*_2\\*.*"},
				new String[]{});
		
		ds2.setSelectors(new FileSelector[]{
				new FileSelector() {
					@Override
					public boolean isSelected(File arg0, String arg1, File arg2)
							throws BuildException {
						return arg1.endsWith(".java");
					}
				}
		});
		
		scanAndPrint("Example 2", ds2);
		
		// example 3
		final DirectoryScanner ds3 = makeScanner(
				"c:\\",
				new String[]{"c:\\dirscan\\**\\foo?.java", "\\dirscan\\**\\foo?.class"},
				new String[]{});

		scanAndPrint("Example 3", ds3);
		
		// example 4
		final DirectoryScanner ds4 = makeScanner(
				"c:\\dirscan",
				new String[]{"c:\\dirscan\\**\\foo?.java", "\\dirscan\\**\\foo?.class"},
				new String[]{});

		scanAndPrint("Example 4", ds4);
	}
}

結果を確認する

上記のコードを実行すると下記のような出力が得られます。

Example 1:
foo1\foo1.class
foo1\foo1_1\foo1_1.class
foo1\foo1_2\foo1_2.class
foo2\foo2.class
foo2\foo2_1\foo2_1.class
foo2\foo2_2\foo2_2.class

1つ目の例は、包含パターン(includes)と除外パターン(excludes)を指定して検索するものです。特段説明は不要でしょう。

Example 2:
bar1\bar1_2\bar1_2.java
foo1\foo1_2\foo1_2.java
foo2\foo2_2\foo2_2.java

2つ目の例では、包含パターンは設定していますが、除外パターンには空の配列を設定しています。これだけだと包含パターンにマッチするすべてのファイルとディレクトリが検索結果に含まれることになりますが、独自のセレクター(FileSelector)を設定して選別処理を追加しています。

Example 3:
dirscan\foo1\foo1.class
dirscan\foo2\foo2.class

3つ目の例では、ベース・ディレクトリの設定を"c:\"に変更するとともに、以下の2つ形式の包含パターンを設定しています。

  1. 先頭にドライブ・レター + パス・セグメントの区切り文字を付ける形式
  2. 先頭にパス・セグメントの区切り文字のみを付ける形式

出力内容には .classファイルだけしかありません。ドライブ・レターを包含パターンに含めてはいけないことが分かります。

Example 4:

4つ目の例では、ベース・ディレクトリの設定を"c:\dirscan"に戻すとともに、3つ目の例と同じ包含パターンを設定しています。結果、出力はなし。検索結果は0件でした。したがって、パターンで設定するパスは、あくまでもベース・ディレクトリからの相対パスとして扱われることが分かります。

ちなみに・・・

今回の検証では、DirectoryScanner#getIncludedFiles() メソッドしか使用していませんが、Apache AntのJavadocを参照すれば明らかなように、ディレクトリとファイルのそれぞれについて、最終的な検索結果として選ばれた(残された)ものとそうでないものを取得するために、個別にメソッドが用意されています。