2016年度 発展プログラミング演習 第8講 Findプログラム 2日目

本日の内容

  1. コマンドライン引数を解析する.
  2. ファイル,ディレクトリの種別で検索できるようにする.
  3. ファイル,ディレクトリを名前で検索できるようにする.
  4. ファイルをファイルサイズで検索できるようにする.
  1. 練習問題
  2. 本日のまとめ

3. コマンドライン引数を解析する.

3-1. コマンドライン引数を解析する.

ここでは,2-1で作成した parseArgumentsメソッドを詳細に実装していきます. 2-1では,args配列の全ての要素を Argumentsdirectoryフィールドに代入していました. それをコマンドラインで渡されたオプションに応じて, Argumentsの実体に設定していきます.

Arguments parseArguments(String[] array){
    Arguments args = new Arguments();
    args.directory = new File(array[0]);
    for(Integer i = 1; i < array.length; i++){
        if(Objects.equals(array[i], "-help")){
            args.help = true;
        }
        else if(Objects.equals(array[i], "-type")){
            i++;
            args.type = array[i];
        }
        // その他のオプションも同様に解析する.
    }
    return args;
}

さて,前回,暫定的にparaseArgumentsの内容を書きました. ここでは,本格的にコマンドラインオプションを解析します. 前回までのparseArgumentsの内容は,これから書くプログラムに置き換えてください.

まず,3行目で,コマンドラインオプションとして渡された0番目の要素を File型に変換し,Arguments型の directoryフィールドに代入しています. 仕様では,最初にディレクトリを指定することになっています. そして,Javaのコマンドライン引数は,前回示した通り, 0番目の要素から始まります.

parseArgumentsに渡されたString 型の配列の各要素について,コマンドラインで指定されたオプションを判定します. 0番目の要素はディレクトリの指定であり,オプションの指定は1番目以降ですので, ループの開始インデックスが1になっている点に注意してください. 左のプログラムのように,コマンドライン引数のString 型の配列の要素が,プログラムのオプションと一致するかを確認します. 文字列の比較は,図書館システム 2日目変数の比較で勉強したように, 変数が指し示す先の一致性を見るのか,内容の一致性を見るのかを区別しましょう. ここでは,内容の一致性を見るため,Objects.equalsメソッドを利用します.

また,-helpオプションは,指定されているか否かの判定だけで良いですが, 他の,例えば,-typeオプションは,引数が要求されます. そのため,要素のインデックスを一つ増加させてオプションの引数を解析しています.

なお,ここでは,オプションの指定方法が間違ったとしても,エラーとなりません. オプションの引数が適切に指定されているかを確認するにはどうすれば良いかを考えてみましょう.

3-2. 文字列をInteger型に変換する.

その他のオプションも同様に解析しようとすると困ったことがおきます. Arguments型のフィールド,maximumminimum は,Integer型であり,String型ではありません. そのため,args.maximum = array[i];で, 互換性のない型とコンパイルエラーとなってしまいます. 文字列(String型)の"123"Integer型の123は異なる値です. この間の相互変換を行わなければいけません.

String numberString = "123";
Integer number = new Integer(numberString);
String string = number.toString();

文字列からIntegerへの変換は,左のように,Integer 型を初期化する時に文字列を渡せば実現できます. もう一方の変換であるInteger型から文字列への変換は, toStringメソッドを呼び出すことで実現できます. ただし,文字列からInteger型への変換で, 数値に変換できない文字列を渡した場合,実行時にエラーが発生します.

また,Integer型から文字列へは,toStringを利用しますが, このメソッドは,実はあらゆる変数で利用できます. どんな変数であっても,toStringメソッドの呼び出しは成功し, その変数の文字列表現が入手できます. その文字列表現が人に理解できる表現かどうかは別の問題ですが.

さて,この変換方法を踏まえて,コマンドラインオプションを解析し, maximumminimumに値を代入しましょう.

4. ファイル,ディレクトリの種別で検索できるようにする.

4-1. ファイルの種別で絞り込む.

では,まず,ファイルの種別での絞り込み,すなわち,コマンドラインのオプションのうち, -typeでの絞り込みを実現します. -typeオプションは,オプションの引数として,dもしくは, fが指定できます.dが指定された場合は, ディレクトリのみを出力し,fが指定された場合は,ファイルの身が出力されるようになります.

void traverse(Arguments args, List<File> result, File file){
    // 「ディレクトリを横断するメソッドを作成する」での作成内容を修正する.
    if(this.isTarget(args, file)){
        result.add(file);
    }
    // 「ディレクトリを横断するメソッドを作成する」で作成した内容が続く.
}
Boolean isTarget(Arguments args, File file){
    if(args.type != null){
        // 引数の file が args.type に合致した種別か判定する.
        // 合致しなかった場合,false を返す.
    }
    // 他の検索処理をここに書いていく.
    return true;
}

オプションの解析は,3-1で行いました. 次に行うべきことは,結果のリストへの追加の際の絞り込みです. 現状では,全てのファイル,ディレクトリがtraverse メソッドの最初で,結果のリストに追加されます. ここで,結果に追加すべきか否かを判定する処理を追加しましょう.

では,Boolean型を返すisTargetメソッドを定義しましょう. 引数はFile型とArguments型とします. 引数に受け取ったFileが結果に含めるべきものであれば,trueを返し, 結果に含めないのであれば,falseを返します. 結果に含める,含めないの条件は,Argumentsが保持しています.

ここで,Argumentstypeフィールドが nullではない場合に判定処理を行います. 判定は,Arguments型のtypeフィールドの値と Fileが指し示すパスの種類で行います. どのような処理を書けば良いかを考えてみましょう.

また,このFileFinderでは,コマンドライン引数から複数の検索条件が指定できます. これらはAND検索ですので,指定された全ての条件に合致した場合にのみ, 結果となるリストに追加されます. つまり,一つでも条件がマッチしなければ条件全体がfalseとなります. そのため,検索条件を判定していき,一致しなければfalseを返し, 一番最後にtrueを返せば全ての条件で判定できることになります.

5. ファイル,ディレクトリを名前で検索できるようにする.

5-1. ファイル,ディレクトリの名前を取得する.

ここでは,4-1で定義した isTargetメソッドを拡張します. 4-1では,ファイルの種別で,結果の絞り込みを行いました. ここでは,別の条件を追加します.ファイル,ディレクトリの名前で検索する方法です. そのため,ファイル,ディレクトリの名前を取得する方法を学習します.

ファイルの名前は,File型の変数に対して,getName() メソッドを呼び出すことで取得できます. getName()メソッドは引数なし,返り値はString型です. パス名は含まれずファイル名のみを返します.パス名も含めた名前を取得したい場合は, getPath()を利用しましょう.

5-2. ファイル・ディレクトリ名で絞り込む.

ファイル名の取得方法がわかったところで,ファイル名での絞り込みを行いましょう. 4-1で作成したisTarget を修正してください.

現在,isTargetメソッドの内容は,Arguments のフィールドtypeが設定されているか(nullでないか) で処理を行っているはずです. そこに,今度は,Argumentsのフィールドname が設定されている場合に,処理を追加しましょう. 比較するのは文字列ですから,Objects.equalsメソッドを利用して比較しましょう. Argumentsのフィールドnameの型はStringです. 比較対象となるのは,Fileではありません.型が違うため,比較できないためです. ファイルの名前を比較するのですから,File型の変数からファイル名を表す String型の実態を取得しましょう.getNameで取得できるのでしたね.

6. ファイルをファイルサイズで検索できるようにする.

この処理は,4. ファイル,ディレクトリの種別で検索できるようにする5. ファイル,ディレクトリを名前で検索できるようにする が実現できていれば,特に難しいことはないでしょう.

ファイルサイズは,File型の変数に対して, lengthメソッドを呼び出せば取得できます. lengthメソッドの返り値の型はLong型です. 以前までと同じく,isTargetメソッドに検索条件を追加してください.

以上の処理が実現できれば,コマンドラインオプションの指定を変え, 特定のファイルが見つかるかを確認してください.

なお,ArgumentsmaximumminimumInteger型,ファイルサイズはLong型です. 本来であれば,型が異なるため,比較はできません. しかし,両方の型とも,数値という共通概念があります. C言語でもint型とdouble型など,異なる型であっても, 自動的に拡張してくれました.Javaでも同様に,数値に関しては, 扱える範囲の広い方に合わせて拡張してくれます.

すなわち,Integerは32ビット,Long型は64ビットで, 両方とも,整数を扱う型です.Longの方がビット数が大きいため, Longの方が扱える範囲が広いです.そのため,IntegerLongで比較,計算を行うと,計算の途中でLong に型を合わせてから計算,比較が行われます.その結果,ユーザは気にせず比較が行えるようになっています.

練習問題

1. helpオプションを実現する.

1-3でコマンドラインのオプションを示し, 3-1でコマンドラインオプションを解析しました. この練習問題では,helpオプションが指定された場合の処理を実現してください.

helpオプションが指定されると,ヘルプメッセージを表示し, 検索を行わずに終了します.どこにどのような処理を入れれば良いかを考えてください.

Javaプログラムを終了するには,mainメソッドが最後まで実行されれば,自動的に終了します. また,Webページを検索すると,Javaプログラムを終了する命令として, System.exit(0);が紹介されているのを見つけるかもしれません. しかし,この命令は使ってはいけません.その理由はFAQの 「なぜSystem.exitを使ってはダメなのでしょうか」 に示します.使わずに実現してください.

ヘルプメッセージは,以下のように 1-3 に示すメッセージをそのままSystem.out.printlnで出力すれば良いです.

void showHelp(){
    System.out.println("java FileFinder <DIR> [OPTIONS]");
    System.out.println("DIR:");
    System.out.println("    検索を開始するディレクトリを指定する.必須項目.");
    System.out.println("OPTIONS:");
    System.out.println("    -help:            ヘルプメッセージを表示して終了する(検索しない).");
    System.out.println("    -name <NAME>:     ファイル名・ディレクトリ名で検索する.NAMEは必須.");
    System.out.println("    -type <d|f>:      ファイルの種別で検索する.dはディレクトリ,fはファイルを指す.");
    System.out.println("                      dもしくはfのどちらかを必ず指定する.");
    System.out.println("                      このオプション自体を指定しない場合,両方を検索する.");
    System.out.println("    -maximum <SIZE>:  ファイルサイズが指定のサイズ(SIZE)より小さいものを検索する.");
    System.out.println("    -minimum <SIZE>:  ファイルサイズが指定のサイズ(SIZE)より大きいものを検索する.");
    System.out.println("    -grep <STRING>:   内容に指定された文字列(STRING)が含まれているファイルを検索する.");
}

2. FileInfoを作成する.

$ ls -l FileInfo.*
-rw-r--r--  1 tamada  staff  1062  5 26 16:39 FileInfo.class
-rw-r--r--  1 tamada  staff   502  5 26 16:39 FileInfo.java
$ java FileInfo FileInfo.java
FileInfo.java: file, 378 bytes
$ java FileInfo FileInfo.*
FileInfo.class: file, 1062 bytes
FileInfo.java: file, 502 bytes
$ java FileInfo .
.: directory, 748 bytes

ファイルの情報を出力するコマンドであるFileInfoを作成しましょう. 左図のように,ファイル名とファイルの種別,ファイルサイズを出力してください. ファイルサイズは,ファイル,ディレクトリを区別せずに取得できます.

本日のまとめ

今日,学んだ内容は次の通りです.