徒然

日常を垂れ流します。

Apache Commons Net listFiles() の注意点

JavaFTPサーバーのデータを扱う場合、Apache Commons Net APIを使うのが一般的(多分)。

FTPというのはHTTPと同じように通信を行うときの約束事(Protocol)のひとつで、

File Transfer Protocolの略である。HTTPのコマンド(GET,POST)と同じように、FTPにもコマンドがある(コマンド一覧)。

例えば、CWDコマンドでディレクトリを移動して、LISTコマンドでそのフォルダにあるファイルの一覧(もしくは、ファイル自体)を取得できる。

ただここで一つ懸念があって、LISTコマンドで取得できるファイル情報は、サーバー側のOSに依存した結果だったりする。つまりFTPサーバーが運用の途中で切り替わったりすると大本から回収が必要になる。

これじゃ悲しい結末を迎えるのが確定的に明らかなので、そのOS間の差を吸収できるようにしたのがApache Commons Net APIFTP関連のクラスたちである。

恐らく多くのシステムでこのAPIは使われていて、信用度はかなりのものだと思う。

だが、これで取得したデータでNullぽしてしまったのである。えーーーー。

 

org.apache.commons.net.ftp.FTPClient

上記のクラスで、相手のホストへの接続、ログイン、ファイル転送すべてが行える。いろいろクラスはあるが、基本的にこれさえ覚えればもう大丈夫。簡単にFTPサーバーへとアクセスできる。

この中のメソッドに、listFiles()というものがあり、上記のLISTコマンドで取得したファイル一覧を取得してFTPFile型の配列を返してくれる。相手がCentOSであれUbuntuであれ、WindowsServerであれ、後も先も同じメソッドが使えるということである。

便利なメソッドlistFiles()だが、一つ注意点がある。もしファイル情報が取得できなかったら、こいつ、配列にnullを入れてくる

つまり、こんな感じの処理をすると...

FTPFile[] files = client.listFiles();    // ファイル一覧取得
for (FTPFile file : files) {             // もし、filesのどれかがnullだったりしたら
    System.out.println(file.getName());  // .getName()でぬるぽ
}

最近、ぬるぽしか診てなくてミスターぬるぽと呼ばれはじめた。

やあ、nullさん。いつも仕事をくれてありがとう。もう出てこなくていいよ。

 

Javadocもそう言っている

閑話休題Apache Commonsの公式ドキュメントを見てみる。

Returns:

The list of file information contained in the current directory in the format determined by the autodetection mechanism.

NOTE: This array may contain null members if any of the individual file listings failed to parse. The caller should check each entry for null before referencing it.

公式ドキュメント:FTPClient#listFiles()より)

もしファイルのパーシング(OS間の差をなくしたりして、FTPFile型の値に直す処理)に失敗したら、nullを入れる*1から、値を参照する前にちゃんとnullチェックしてね。ということだ。公式が言っているんだから、ちゃんとチェックしないといけないね(2回目)。

にもかかわらず、いま診てるシステムではしていなかったのだなあ。ネットに転がっているサンプルコードでも、nullチェックしているものは少ない。(そもそもこのAPIに関する記事が少ないけどね)

改善案

さっきから愚痴が多いが、直すならこう。

FTPFile[] files = client.listFiles();    // ファイル一覧取得
for (FTPFile file : files) {             // もし、filesのどれかがnullだったりしたら
    if(file == null)
        continue;                        // 最初に戻る
    System.out.println(file.getName());  // .getName()でぬるぽらない!
}

周りの人を困らせたいならこう

Arrays.asList(client.listFiles())       // ファイル一覧取得
    .stream()
    .filter(Objects::nonNull)     // nullをひっかけて
    .map(FTPFile::getName)        // Stream<String>にして
    .forEach(System.out::println);// わぁい!

*1:ちなみに、具体的にどんなファイルがnullになるかはよくわからない。文字コードが変わってしまったり、ファイル名に禁止文字が入ったりしたときかな?(これもしくはこれ