OSの関連付けに従ってファイルを開く

JavaからOSの関連付けに従ってファイルを開くにはどうすれば良いか。例えば、指定したxlsファイルをそれが関連付けられているExcelで開くための方法。

Runtime#exec(パス) → ダメ。

最初に考えたのは、普通にexeを起動させるやり方をそのまま使う方法。

Runtime.getRuntime().exec("c:\\test.xls");

これはダメだった。xlsは実行可能ファイルではないため、エラーになった。

cmd /cを経由 → 一応動いてるように見えるが、問題あり。

そこで、こんな方法があるにはある。

Runtime.getRuntime().exec("cmd /c \"c:\\test.xls\"");

cmdに/cオプションを渡すと、その後ろに渡したパスを開いてくれる。なのでそのコマンドをRuntime#execで呼び出すわけだ。

これはcmdが使える環境に限定される。つまり最近のWindows。他のOSでもやりたいなら、似たようなコードをOSの数だけ記述して条件で分岐させる必要がある。

また、別な問題もある。

Process p = Runtime.getRuntime().exec("cmd /c \"c:\\test.xls\""); // ここで Excel でファイルを開く。
p.waitFor(); // その Excel が終了するまで処理がここで止まる。
System.out.println("終了しました。");

Runtime#execはProcessのインスタンスを返す。このProcessクラスは、waitForメソッドを呼ぶことで、そのプロセスが終了するまで待つことができる。

上記のコードもうまく動くように見えるが、このコードが実行される時点で既に他のExcelが起動しているとうまく動かない。既にExcelが実行中の場合、そのExcelのプロセスを使ってtest.xlsを開いてしまい、新しいプロセスが立ち上がらない。そしてp.waitFor()が空振りする。

証拠は無いけど、もしかして以下のようなことが起こってるのではないか。

  1. 新しいExcelプロセスが立ち上がる。
  2. 既に起動中のExcelプロセスを発見する。
  3. 新しいプロセスは、既に起動中のプロセスの方でファイルを開くように指示を出す。
  4. 既に起動中のプロセスは、指示を受けたのでファイルを開く。
  5. 新しい方のプロセスはすぐに終了する。

こういうことが起きてると考えると、p.waitFor()が空振りする理由が説明つく。新しいプロセスはファイルを開かずにすぐに終了してしまうため、p.waitFor()が一瞬で成立してしまっているということ。

いずれにしても、起動したアプリが終了するまで待ちたい場合はこの方式は使えないことになる。

java.awt.Desktop#edit → ベストなやり方のようだけど、うまく行かない。

Java 6から、java.awt.Desktopというクラスが追加された。

これによると、Desktop#editメソッドを使うと、OSの関連付けに従ってファイルを開いてくれるらしい。Java 6以降に限定されるが、これは使えそうだと思った。

File file = new File("c:\\test.xls");
Desktop desktop = Desktop.getDesktop();
desktop.edit(file);

こんなコードを書いてみた。すると、

java.io.IOException: Failed to edit file:/c:/test.xls. Error message: この操作に対して指定されたファイルには、アプリケーションが関連付けられていません。

という例外になった。どうやらJava的にxlsは関連付けされてないらしい。もちろん、普通にWindows上では関連付けされているのに。

ファイル名をtest.txtに変えてやってみたら、無事メモ帳が起動した。つまりtxtはOKなのにxlsはNG。なんだこれ。全然使えない。

追記 : openメソッド
File file = new File("c:\\test.xls");
Desktop desktop = Desktop.getDesktop();
desktop.open(file);

editメソッドはダメだったけど、openメソッドを使ってみたところ、Excelが開いた。「全然使えない」とか言ってゴメン。

ただ、このやり方ではExcelが終了するまで待つ方法が無い。

困った

結局、いい方法が見つからない。