Apache Commons Net listFiles() の注意点
JavaでFTPサーバーのデータを扱う場合、Apache Commons Net APIを使うのが一般的(多分)。
FTPというのはHTTPと同じように通信を行うときの約束事(Protocol)のひとつで、
File Transfer Protocolの略である。HTTPのコマンド(GET
,POST
)と同じように、FTPにもコマンドがある(コマンド一覧)。
例えば、CWD
コマンドでディレクトリを移動して、LIST
コマンドでそのフォルダにあるファイルの一覧(もしくは、ファイル自体)を取得できる。
ただここで一つ懸念があって、LIST
コマンドで取得できるファイル情報は、サーバー側のOSに依存した結果だったりする。つまりFTPサーバーが運用の途中で切り替わったりすると大本から回収が必要になる。
これじゃ悲しい結末を迎えるのが確定的に明らかなので、そのOS間の差を吸収できるようにしたのがApache Commons Net APIのFTP関連のクラスたちである。
恐らく多くのシステムでこの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.
もしファイルのパーシング(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);// わぁい!