S-JIS[2009-01-22] 変更履歴

Conditionインターフェース

Ant自作タスクでcondition系タスク(and・or・not等)を扱うインターフェース。
And・Or・NotはConditionBaseクラスから派生し、Conditionを実装している。

Conditionにはbooleanを返すeval()というメソッドがあり、各クラスはeval()を実装して、条件が真のときにtrueを返す。

ConditionBaseは他のConditionの一覧(リスト)を保持・内包できるようになっている。
例えばAndは、その一覧全てのeval()がtrueの時にtrueを返す。

自分でもConditionインターフェースを実装したクラスを作れば、同様に扱うことが出来る。


ファイルの中から文字列を探すタスクの例

いわばUNIXのgrep(やDOSのfindstr)のようなタスクを作ってみる。

build.xmlのイメージ:

	<taskdef name="find" classname="jp.hishidama.sample.ant.find.FindTask" loaderref="f"
		classpath="classes" />
	<typedef name="fstr" classname="jp.hishidama.sample.ant.find.FindString" loaderref="f" />
	<target name="find">

		<find dir="C:\temp" encoding="MS932">
			<include name="**/*.txt" />
			<fstr text="abc" />	←検索文字列の指定
		</find>
	</target>
		<!-- 複数の条件を指定する例 -->
		<find dir="C:\temp" encoding="MS932">
			<include name="**/*.txt" />
			<and>
				<fstr text="abc" />
				<fstr text="def" />
			</and>
		</find>

FindString.java:

ipackage jp.hishidama.sample.ant.find;

import java.util.List;

import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.PropertyHelper;
import org.apache.tools.ant.taskdefs.condition.Condition;
import org.apache.tools.ant.types.DataType;
/**
 * 探したい文字列を保持する条件クラス.
 */
public class FindString extends DataType implements Condition {
	protected String text;

	/** 検索対象文字列をセットする. */
	public void setText(String s) {
		text = s;
	}
	public boolean eval() throws BuildException {
		// プロパティーからファイルの内容を取得する
		PropertyHelper ph = PropertyHelper.getPropertyHelper(this.getProject());
		List list = (List)ph.getUserProperty(null, FindTask.LIST_KEY);

		for (int i = 0; i < list.size(); i++) {
			String s = (String)list.get(i);
			if (s.indexOf(text) >= 0) {
				return true; //文字列が見つかったらtrueを返す
			}
		}
		return false;
	}
}

判断を行うeval()(他クラスから呼ばれる)には引数が無いので、“ファイルの内容”といった、実行時に色々変わる値は直接は渡せない。
そこで、プロパティーを利用することにした。
Javaのシステムプロパティーを使うとJavaVM内で共通となってしまうので、PropertyHelper(Ant1.6以降)を使うのが良いと思われる。これはProject毎にプロパティーを保持できる。
もしスレッド毎に持ちたいなら、ThreadLocalでも使うかなぁ。

ProjectクラスにもgetUserProperty()はあるのだが、これはStringしか扱えない。
PropertyHelperならObjectを入れられる。
(ProejctのgetUserProperty()も、内部ではPropertyHelperを使っている)

本当は、eval(Object)といった引数付きメソッドにして、データを渡せるようになっていれば良かったと思う…。

リフレクションを使えば無理矢理データをセットしておける


FindTask.java:

package jp.hishidama.sample.ant.find;

import java.io.*;
import java.util.*;

import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.DirectoryScanner;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.PropertyHelper;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.taskdefs.condition.And;
import org.apache.tools.ant.taskdefs.condition.Condition;
import org.apache.tools.ant.taskdefs.condition.Not;
import org.apache.tools.ant.taskdefs.condition.Or;
import org.apache.tools.ant.types.FileSet;
import org.apache.tools.ant.util.FileUtils;
/** 複数ファイルから文字列を検索するタスク. */
public class FindTask extends Task {
	protected String encoding;

	public void setEncoding(String s) {
		encoding = s;
	}
	// FileSetクラスを使用
	protected List fss = new ArrayList();

	public void addFileset(FileSet set) {
		fss.add(set);
	}
	// 条件をセット
	protected Condition cond;

	public void addConfigured(Condition c) {
		if (cond == null) {
			cond = c;
		} else if (cond instanceof Or) {
			((Or) cond).add(c);
		} else {
			Or or = new Or();
			or.setProject(this.getProject());
			or.add(cond);
			or.add(c);
			cond = or;
		}
	}

ここでは、ボディー部に複数のCondition実装クラスを指定した場合に、orで繋いでいるものとして扱っている。
他にaddConfiguredAnd()等も必要
 

	// 実行!
	public void execute() throws BuildException {
		validate();

		for (int i = 0; i < fss.size(); i++) {
			FileSet fs = (FileSet)fss.get(i);
			DirectoryScanner ds = fs.getDirectoryScanner();
			String[] files = ds.getIncludedFiles();

			for (int j = 0; j < files.length; j++) {
				File f = new File(ds.getBasedir(), files[j]);
				execute(f); // 個別ファイルの処理
			}
		}
	}
	/** 精査ロジック */
	protected void validate() throws BuildException {
		if (fss.size() == 0) {
			throw new BuildException("fileset must be set.");
		}

		// ボディー部のConditionをチェック
		validateCondition(this.cond);
	}
	/** ボディー部のConditionを再帰的にチェック …したいのだが… */
	protected void validateCondition(Condition c) throws BuildException {
		if (c == null) {
			throw new BuildException("condition must be set.");
		}
		if (c instanceof FindString) { //オリジナル条件は許可
			return;
		}

		// and,or,notのみ許す
		if (c instanceof And || c instanceof Or || c instanceof Not) {
			// 各Conditionの中を再帰的にチェック!
//			ConditionBase cb = (ConditionBase) c;
//			Enumeration n = cb.getConditions(); // コンパイルエラー!
//			while (n.hasMoreElements()) {
//				validateCondition((Condition)n.nextElement());
//			}
			return;
		} else {
			throw new BuildException(c.getClass().getSimpleName() + " can not be used.");
		}
	}

本当はcb内に保持されているリストに対して再帰的にvalidateCondition()を適用したい (and,or,notとFindString以外のConditionを拒否したい)
のだが、cb.getConditions()はprotectedなのでアクセスできない!
リフレクションで何とかしてみる方法
 

	static final String LIST_KEY = FindTask.class.getName() + "#list";

	/**
	 * ファイル処理
	 *
	 * @param f ファイル
	 * @throws BuildException
	 */
	protected void execute(File f) throws BuildException {
		log(f.getAbsolutePath(), Project.MSG_VERBOSE);

		List list = read(f);
		PropertyHelper ph = PropertyHelper.getPropertyHelper(this.getProject());
		ph.setUserProperty(null, LIST_KEY, list);

		// 条件を満たした場合、ファイル名をログ出力する
		if (cond.eval()) {
			log(f.getAbsolutePath(), Project.MSG_INFO);
		}
	}

条件判断を行うFindStringクラスへファイルの内容(ファイル毎に変わる)を渡す為にPropertyHelperを使っている。
 

	/** ファイルを読み込む */
	protected List<String> read(File f) throws BuildException {
		InputStream is = null;
		InputStreamReader ir = null;
		BufferedReader br = null;
		try {
			br = new BufferedReader(ir = new InputStreamReader(is = new FileInputStream(f), encoding));

			List list = new ArrayList();
			for (;;) {
				String s = br.readLine();
				if (s == null) break;
				list.add(s);
			}
			return list;

		} catch (IOException e) {
			throw new BuildException(e);
		} finally {
			FileUtils.close(br);
			FileUtils.close(ir);
			FileUtils.close(is);
		}
	}
}

Antの場合、FileUtils.close(obj)でnullチェックおよびclose()時の例外の握りつぶしを行ってくれるので、(自分で用意しなくて済むので)便利♪

ちなみに、FileUtilsにはReaderから全データを読み込んで一つのStringにして返すメソッドもあるので、この例ではファイル内から文字列を検索したかったのでそれでも良かった。
ただ、今回はプロパティー経由でString以外を渡してみたかったので、あえて使わなかった。


addConfiguedXXX()

しかし上記のようにCondition系クラス保持の為にaddConfigured(Condition)しか無いと、andタグを使った場合に実行時にエラーになる。

BUILD FAILED
build.xml:154: and doesn't support the nested "fstr" element.

何故かと言うと、デフォルトのandタグは、どうやらConditionのAndクラスではなくて、FileSelectorのAndSelectorクラスなんだなー(嘆)
AndSelectorはConditionを保持するようには出来てないから、fstrFindString)は当然マッチせず、エラーとなる。

なので、自分で「And」が付いたaddメソッドを用意してやると、一応こちらが呼ばれるようになるようだ。

	public void addConfiguredAnd(And c) {
		addConfigured(c);
	}

	public void addConfiguredOr(Or c) {
		addConfigured(c);
	}

	public void addConfiguredNot(Not c) {
		addConfigured(c);
	}

(ちなみに、MatchingTaskではadd()の他にaddAnd()addOr()といったメソッドが一通り定義されている)


Conditionの精査

validateCondition()で、保持されている条件インスタンスを全て調べて、無関係なものがあったらエラーとしたい。

		// and,or,notのみ許す
		if (c instanceof And || c instanceof Or || c instanceof Not) {
			// 各Conditionの中を再帰的にチェック!
			ConditionBase cb = (ConditionBase) c;
			Enumeration n = cb.getConditions(); // コンパイルエラー!
			while (n.hasMoreElements()) {
				validateCondition((Condition)n.nextElement());
			}
			return;
		} else {
			throw new BuildException(c.getClass().getSimpleName() + " can not be used.");
		}

しかし、ConditionBase#getConditions()はprotectedメソッドなのでアクセスできない!

この場合、リフレクションを使えばprotectedメソッドにもアクセスできる。ちょっと面倒だが仕方ない。

		// and,or,notのみ許す
		if (c instanceof And || c instanceof Or || c instanceof Not) {
			// 各Conditionの中を再帰的にチェック!
			Enumeration n = getBaseConditions(c);
			while (n.hasMoreElements()) {
				validateCondition((Condition)n.nextElement());
			}
			return;
		} else {
			throw new BuildException(c.getClass().getSimpleName() + " can not be used.");
		}
	protected Method GetConditionMethod;

	protected Enumeration getBaseConditions(Object obj) {
		try {
			if (GetConditionMethod == null) {
				GetConditionMethod = ConditionBase.class.getDeclaredMethod("getConditions", (Class[]) null);
				GetConditionMethod.setAccessible(true);
			}
			return (Enumeration) GetConditionMethod.invoke(obj, (Object[]) null);
		} catch (Exception e) {
			throw new BuildException(e);
		}
	}

ただし、当然セキュリティーポリシー(セキュリティーマネージャー)が登録されてセキュリティーが有効になっている場合は、エラーになって実行できない。
でもAnt自体がセキュリティーを有効にしていると実行できないから(爆)、別に気にしなくてもいいかな?


※この方法を使えば、プロパティーを使わなくても個々のオブジェクトにデータを渡せるね 。

	protected void validateCondition(Condition c) throws BuildException {
		if (c == null) {
			throw new BuildException("condition must be set.");
		}
		if (c instanceof FindString) {
			// FindTaskインスタンスを渡しておき、getList()のようなメソッドを用意しておけばよい
			((FindString)c).setFindTask(this);
			return;
		}

		// and,or,notのみ許す
		if (c instanceof And || c instanceof Or || c instanceof Not) {
			
		}
	}

独自タスクの作り方へ戻る / Ant目次へ戻る / 技術メモへ戻る
メールの送信先:ひしだま