以前の日記でも書きましたが、
package-info.javaはインタフェースとしてコンパイルされます。
で、"package-info"という名前だけあって、パッケージ自身のメタ情報を含めるのにちょうど良い。
今やっている仕事で、『特定の処理を全てのクラスに適用する』というオーダーがありました。
コードで書くと、まあこんな感じの内容。
public class DoSomethingToAllClasses { // 指定したディレクトリ配下のすべてのクラスに対して処理を行う public void execute(File rootDir) { for (final Class<?> clazz : findAllClasses(rootDir)) { this.doSomething(clazz); } } // 何かの処理 private void doSomething(final Class<?> clazz) { ... } // 指定したディレクトリ配下のクラスを検索する private Set<Class<?>> findAllClasses(final File dir) { final Set<Class<?>> classes = new HashSet<Class<?>>(); for (final File f : dir.listFiles()) { if (f.isDirectory()) { classes.addAll(this.findAllClasses(f)); } else if (f.getName().endsWith(".class")) { classes.add(this.resolveClassFromClassFile(f)); } } return classes; } private Class<?> resolveClassFromClassFile(final File classFile) { ... } }
これで、要求は満たせました。ところが。。。
一部のパッケージだけは除外したい
という処理を追加することに。
で、一番単純なのがこういうやり方。
public class DoSomethingToAllClasses { // 無視するパッケージ private static final Set<Package> IGNORE_PACKAGES = new HashSet<Package>(Arrays.asList( Package.getPackage("foo.aaa"), Package.getPackage("foo.bbb") )); //...(変更がない箇所は省略 ) private Set<Class<?>> findAllClasses(final File dir) { final Set<Class<?>> classes = new HashSet<Class<?>>(); outer: for (final File f : dir.listFiles()) { if (f.isDirectory()) { classes.addAll(this.findAllClasses(f)); continue; } else if (!f.getName().endsWith(".class")) { continue; } final Class<?> clazz = this.resolveClassFromClassFile(f); // 無視パッケージに含まれるクラスなら無視する for (final Package ignorePackage : IGNORE_PACKAGES) { if (this.contains(ignorePackage, clazz)) { continue outer; } } classes.add(clazz); } return classes; } // クラスが、指定したパッケージまたはそのサブパッケージに含まれるか否か private boolean contains(Package p, Class<?> clazz) { // "p==null"→デフォルトパッケージ if (p == null) return true; else if (clazz.getPackage() == p) return true; final Package parent = Package.getPackage(p.getName().replaceFirst("[.]\\w+$", "")); return this.contains(parent, clazz); } }
これで問題ないのですが、無視対象のパッケージが増えるたびに
DoSomethingToAllClassesを修正する(IGNORE_PACKAGESのエントリを増やす)必要があります。
それがイヤならば、
『DoSomethingToAllClasses#executeの引数に、無視パッケージ一覧を渡してやればいいのでは?』
という話になるのですが、とある事情があり、それもできない。さてどうするか。
パッケージにアノテーションを付ける
最終的に実施したのが、
『package-info.javaを用意し、パッケージにアノテーションを付けて無視パッケージか否かを判定する』
という方法。具体的には次の通りです。
/** * 無視するパッケージであることを示すアノテーション */ @Documented @Inherited @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.PACKAGE) public @interface Ignore {} public class DoSomethingToAllClasses { //...(変更がない箇所は省略 ) private Set<Class<?>> findAllClasses(final File dir) { final Set<Class<?>> classes = new HashSet<Class<?>>(); outer: for (final File f : dir.listFiles()) { if (f.isDirectory()) { classes.addAll(this.findAllClasses(f)); } else if (f.getName().endsWith(".class")) { final Class<?> clazz = this.resolveClassFromClassFile(f); if (!isIgnoringPackage(clazz.getPackage())) { classes.add(clazz); } } } return classes; } private boolean isIgnoringPackage(Package p) { if (p == null) return false; else if (p.isAnnotationPresent(Ignore.class)) return true; else { final Package parent = ...; return isIgnoringPackage(parent); } } }
package-info.javaはこんな感じ。(foo.barパッケージ)
@Ignore package foo.bar;
これで、無視パッケージが追加されてもpackage-info.javaを追加すればよいだけ、になります。