あるディレクトリ以下の.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年経とうとしていたとは。今後は週一くらいでネタを投下したいです。