あるディレクトリ以下の.classファイルを再帰的にJadるAntタスク

突然ですが、jadってご存知ですか?javaファイルをコンパイルしてできた.classファイルを、元のjavaファイルに戻すためのいわゆる「デコンパイル」のツールです。
今回、jdbcドライバの中身を見たくて、解凍したJarに含まれている.classファイルを一度でデコンパイルしたかったので、Javaの練習を兼ねてAntのタスクにすることにしました。せっかく作ったのでここでさらしておこうと思います。
Antについてはこの辺↓を見ていただくとして、
http://www.techscore.com/tech/Java/ApacheJakarta/Ant/1/

今回作るタスクの、build.xmlでの記述方法はこんな感じです。

<?xml version="1.0" encoding="UTF-8"?>
<project basedir="." default="jad">
	<property name="classes.dir" value="classファイルのルート" />
	<target name="jad">
		<jad basedir="${classes.dir}" recurse="true" executable="">
			<arg value="-lnc" />
			<arg value="-p" />
			<arg value="-s java" />
		</jad>
	</target>

        <!-- taskdefタグで、独自タスクを読み込みます -->
	<taskdef name="jad" 
		classname="ant.jad.JadTask" 
		classpath="./ant-jad.jar"/>
</project>

jadタスクについて簡単に説明すると、

  • basedirで.classファイルのルートディレクトリを指定
  • recurseでディレクトリを再帰的に走査するかを指定(true/false)
  • executableでjad.exeへのパスを指定
  • 子要素のargでは、実行時オプションを指定

といった具合です。

独自タスクを作るには,Taskクラスを継承したクラスを作り、executeメソッドを実装するだけでOK!
そのタスクの本体がこちらです。

/**
 * あるディレクトリをベースに、再帰的にjadるためのタスク
 * @author tetran
 *
 */
public class JadTask extends Task {
	private static final String TARGET_EXTENSION = ".class";
	private String executable;
	private String basedir;
	private RecurseAttr recurse;
	private List<Parameter> args = new ArrayList<Parameter>();
	private File dir;
	private boolean isRecursive;
	private String[] executableArray;

	@Override
	public void execute() throws BuildException {
		validate();
		isRecursive = Boolean.valueOf(recurse.getValue());
		System.out.println(isRecursive);
		setupTemplate();
		try {
			exec(dir);
		} catch (IOException e) {
			e.printStackTrace();
			throw new BuildException("File Access Error!");
		}
	}

	private void setupTemplate() {
		executableArray = new String[args.size() + 2];
		executableArray[0] = isEmpty(executable) ? "jad" : executable;
		System.arraycopy(argsAsArray(), 0, executableArray, 1, args.size());
	}

	private void exec(File dir) throws IOException {
		File[] files = dir.listFiles(dirAndClasses());
		Runtime runtime = Runtime.getRuntime();
		for (File f : files) {
			if (f.isDirectory() && isRecursive) {
				exec(f);
				continue;
			}
			String thisPath = f.getAbsolutePath();
			log("processing-> " + thisPath);
			// 対象ファイル名は配列の最後
			executableArray[executableArray.length - 1] = thisPath;
			runtime.exec(executableArray, null, dir);
		}
		runtime.gc();
	}
	private String[] argsAsArray() {
		String[] result = new String[args.size()];
		for (int i = 0; i < result.length; i++) {
			result[i] = args.get(i).getValue();
		}
		return result;
	}
	/**
	 * ディレクトリとclassファイルを抽出する。
	 * @return
	 */
	private static FilenameFilter dirAndClasses() {
		return new FilenameFilter() {
			@Override
			public boolean accept(File dir, String name) {
				if (new File(dir, name).isDirectory()) {
					return true;
				}
				return name.endsWith(TARGET_EXTENSION);
			}
		};
	}

	private void validate() {
		if (basedir == null || basedir.isEmpty()) {
			throw new BuildException("'basedir' must be designated!");
		}
		dir = new File(basedir);
		if (dir.isDirectory() == false) {
			throw new BuildException("such a directory does not exist");
		}
	}

        // 属性はsetterメソッドで追加する
	public void setExecutable(String executable) {
		this.executable = executable;
	}

	public void setBasedir(String dir) {
		this.basedir = dir;
	}

	public void setRecurse(RecurseAttr recurse) {
		this.recurse = recurse;
	}

        // 要素の追加はaddXXX(Parameter p)を使う
	public void addArg(Parameter arg) {
		this.args.add(arg);
	}

        // EnumeratedAttributeを拡張したクラスを用意することで、
        // 入力値を制限できる & EclipseのAntエディタで補完される!
	public static class RecurseAttr extends EnumeratedAttribute {
		@Override
		public String[] getValues() {
			return new String[] {"true", "false",};
		}
	}

	private static boolean isEmpty (String val) {
		return val == null || val.isEmpty();
	}
}

後はこのクラスをjarに固めて出来上がり!
独自タスクを作るときに大事なポイントとしては、

  • Taskを拡張したクラスを作る
  • executeメソッドを実装する
  • 属性はsetterメソッドで追加する
  • 子要素はaddXXXメソッドで追加する

くらいを押さえておけば十分だと思います。
ライブラリの拡張って、ちょっと敷居が高いような気がしていましたが、やってみると以外といけるものですね!こういう「自前ライブラリ」をどんどん増やしていきたいと思いました。

ていうか、前回の投稿からもう1年経とうとしていたとは。今後は週一くらいでネタを投下したいです。