倭マン's BLOG

くだらない日々の日記書いてます。 たまにプログラミング関連の記事書いてます。 書いてます。

Groovy JDK? It's GDK! (File 編4) ファイル・ディレクトリの走査

今回は GDK が java.io.File クラスに追加している、ファイル・ディレクトリの走査に関するメソッドを見ていきます(目次)。 扱うメソッドは下表の通り:

メソッド 返り値 since
非再帰 eachFile(Closure c)
eachFile(FileType type, Closure c)
eachDir(Closure c)
eachFileMatch(Object nameFilter, Closure c)
eachFileMatch(FileType type, Object nameFilter, Closure c)
eachDirMatch(Object nameFilter, Closure c)
void 1.5.0
1.0
1.7.1
1.5.0
1.5.0
1.7.1
再帰 eachFileRecurse(Closure c)
eachFileRecurse(FileType type, Closure c)
eachDirRecurse(Closure c)
void 1.7.1
1.5.0
1.0
traverse
(再帰)
traverse(Closure c)
traverse(Map options)
traverse(Map options, Closure c)
void 1.7.1

大雑把に言って

  • eachFile() ・・・ ディレクトリ下のファイル・サブディレクトリを走査(再帰なし)
  • eachFileMatch() ・・・ ディレクトリ下のファイル・サブディレクトリのうち、フィルターを通ったもののみ走査(再帰なし)
  • eachFileRecurse() ・・・ ディレクトリ階層を再帰的に走査
  • traverse() ・・・ ディレクトリ階層を再帰的に走査

の4種類があり、each で始まる3つにはそれぞれ

  • ディレクトリを走査する eachDirXxxx()
  • groovy.io.FileType (後述)でファイルの種類を指定して走査する eachFileXxxx(FileType type, ...)

があります。

groovy.io.FileType 定数(enum)

走査するファイルのタイプ(ファイル / ディレクトリ)を指定するために、groovy.io パッケージに FileType という列挙型が定義されています:

package groovy.io

enum FileType {
    FILES,
    DIRECTORIES,
    ANY
}

Any はファイルとディレクトリ両方を走査します。 FileType を指定しない eachFile(), eachFileMatch(), eachFileRecurse() はファイルタイプとして ANY が指定されているのと同じになります。 つまり、ファイルだけでなくディレクトリも走査されるのでご注意を。

eachFileXxxx() メソッド

あれこれ説明するよりもサンプル見た方が手っ取り早いかと思うので、サンプルコードを書こうかと。 で、チョット前に何人かの方が書いてらっしゃった「GroovyでJavaのpackage-info.javaを一括生成するスクリプト」というお題を(というかコード自体を)拝借して、ちょっと書き換えてみました。 拝借もとは

でーす。 ポツポツと余計なものを付け加えてますが、あまり気にしないで下さいな:

import static groovy.io.FileType.FILES

final String PACKAGE_INFO = 'package-info.java'

def template = { pkg -> """\
/**
 * $pkg パッケージ。
 *
 * <pre>
 * // TODO パッケージ内容の詳細を記述してください
 * </pre>
 * 
 */
package $pkg;
""" }

// 実行時に起点となるディレクトリを指定できるようにしてみた
def rootPath = args ? args[0] : 'src'

def srcRoot = new File(rootPath)
println "[SOURCE ROOT] $srcRoot.canonicalPath"
println()
int n = srcRoot.canonicalPath.size() + 1

srcRoot.eachDirRecurse{ dir ->    // eachDirRecurse() ディレクトリを再帰的に走査
    // *.java ファイルがないディレクトリはスキップ
    boolean hasJavaSrc = false
    dir.eachFileMatch(FILES, ~/.*\.java/){ hasJavaSrc = true }    // eachFileMatch() のフィルターに正規表現を使用
    if(!hasJavaSrc)return

    // package-info.java が既にあるディレクトリもスキップ
    def packageInfo = new File(dir, PACKAGE_INFO)
    if(packageInfo.exists())return

    // package-info.java の生成
    def packageName = dir.canonicalPath[n..-1].tr(File.separator, '.')
    packageInfo.text = template(packageName)
    println "[GENERATE] $packageInfo.canonicalPath"
}
println()

// package-info.java の列挙
println '''Existing 'package-info.java' List'''
srcRoot.eachFileRecurse(FILES){ file ->
    if(file.name == PACKAGE_INFO)
        println "+ $file.canonicalPath"
}

3ヶ所でファイルを走査するメソッドを使ってます:

  1. package-info.java を生成するために eachDirRecurse() でディレクトリのみを再帰的に走査
  2. ディレクトリ内に *.java ファイルがないかどうかを eachFileMatch() で走査(無理矢理使ってる感が否めないけどw)
  3. 存在している package-info.java を列挙するために eachFileRecurse() に FileType.FILES を指定して走査

説明が必要そうなのは eachFileMatch() の第2引数に指定しているフィルターでしょうか。 JavaDoc では引数の型として Object が指定されていますが、このオブジェクトには isCase() メソッドが定義されていることが想定しています。 まぁ言ってみれば、Groovy の switch 文で case の後に指定できるものなら何でも OK だということかと。 正規表現や Closure、文字列なんかもいけるようです。 詳しくは「Groovy JDK」の File クラス eachFileMatch() メソッドを参照のこと。

上記のサンプルを実行するとこんな感じ(Windows で C:\workspace\groovy にて):

[SOURCE ROOT] C:\workspace\groovy\src

[GENERATE] C:\workspace\groovy\src\org\sample\package-info.java
[GENERATE] C:\workspace\groovy\src\org\sample\child1\package-info.java
[GENERATE] C:\workspace\groovy\src\org\sample\child2\package-info.java

Existing 'package-info.java' List
+ C:\workspace\groovy\src\org\sample\child1\package-info.java
+ C:\workspace\groovy\src\org\sample\child2\package-info.java
+ C:\workspace\groovy\src\org\sample\package-info.java

traverse() メソッド

traverse() は引数の Map に色々と値を設定して、走査方法をある程度細かく設定できる走査メソッドです。 設定できる項目は「Groovy JDK」参照のこと。 では、上記と同様のサンプルを traverse() を使って書いてみます:

import static groovy.io.FileType.*

final String PACKAGE_INFO = 'package-info.java'

def template = { pkg -> """\
/**
 * $pkg パッケージ。
 *
 * <pre>
 * // TODO パッケージ内容の詳細を記述してください
 * </pre>
 * 
 */
package $pkg;
""" }

def rootPath = args ? args[0] : 'src'

def srcRoot = new File(rootPath)
println "[SOURCE ROOT] $srcRoot.canonicalPath"
println()
int n = srcRoot.canonicalPath.size() + 1

srcRoot.traverse(type:DIRECTORIES){ dir ->    // ディレクトリ階層の走査
    boolean hasJavaSrc = false
    dir.traverse(type:FILES, maxDepth:0, nameFilter:~/.*\.java/){ hasJavaSrc = true }    // *.java ファイルの走査
    if(!hasJavaSrc)return

    def packageInfo = new File(dir, PACKAGE_INFO)
    if(packageInfo.exists())return

    def packageName = dir.canonicalPath[n..-1].tr(File.separator, '.')
    packageInfo.text = template(packageName)
    println "[GENERATE] $packageInfo.canonicalPath"
}
println()

println '''Existing 'package-info.java' List'''
srcRoot.traverse(type:FILES, nameFilter:PACKAGE_INFO){ file ->    // package-info.java の走査
    println "+ $file.canonicalPath"
}

最初のサンプルコードで使っていた eachDirRecurse(), eachFileMatch(), eachFileRecurse() の3ヶ所を traverse で書き換えました:

  1. ディレクトリ階層の走査 ・・・ ディレクトリのみを走査したいので、type に FileType.DIRECTORIES を指定しています
  2. *.java ファイルの走査 ・・・ サブディレクトリを走査しないので maxDepth に0を指定、ファイル名が .java で終わるファイルのみを走査するため nameFilter に対応する正規表現を指定
  3. package-info.java の走査 ・・・ 「package-info.java」という名前のファイルを走査したいので、nameFilter に文字列 "package-info.java" を指定

といったところでしょうか。 「package-info.java の走査」の場合のように、traverse() を使うとディレクトリ階層を走査しつつ名前フィルターなども使えるので、込み入った走査が必要な場合は traverse() を使うとよいかと。

ついでに package-info.java を削除するコードも書いてみました:

import static groovy.io.FileType.FILES

def rootPath = args ? args[0] : 'src'

def srcRoot = new File(rootPath)
println "[SOURCE ROOT] $srcRoot.canonicalPath"
println()

srcRoot.traverse(type:FILES, nameFilter:~/package-info\.java/){ file ->
    file.delete()
    println "[DELETE] $file.canonicalPath"
}

ディレクトリ階層を走査しつつ名前フィルターも使うので、traverse() を使うと簡単に書けます。 いぇ~いっ!

次回は Object input/output stream, Data input/output stream ・・・かな?

プログラミングGROOVY

プログラミングGROOVY

  • 作者: 関谷和愛,上原潤二,須江信洋,中野靖治
  • 出版社/メーカー: 技術評論社
  • 発売日: 2011/07/06
  • メディア: 単行本(ソフトカバー)
  • 購入: 6人 クリック: 392回
  • この商品を含むブログ (155件) を見る
Groovyイン・アクション

Groovyイン・アクション

  • 作者: Dierk Konig,Andrew Glover,Paul King,Guillaume Laforge,Jon Skeet,杉浦孝,櫻井正樹,須江信洋,関谷和愛,佐野徹郎,寺沢尚史
  • 出版社/メーカー: 毎日コミュニケーションズ
  • 発売日: 2008/09/27
  • メディア: 単行本(ソフトカバー)
  • 購入: 5人 クリック: 146回
  • この商品を含むブログ (121件) を見る