2009年09月13日

Javaでカレントディレクトリを変更する

Javaを利用していて、カレントディレクトリを変更したいという方は多いと思います。
しかし、現状のJavaでは基本APIだけではカレントディレクトリを容易に変更することはできません。

ネットで検索するなどして良く出る方法としては以下の3つがあります。

1. user.dirの値を変更する
2. コマンドプロンプト経由でcd(等のOS依存)コマンドを実行する

が、上記方法では変更できません。
対応方法としては以下の2方法で対応可能です。

3. Win32 APIのSetCurrentDirectory()等、OSのAPIをラップしたライブラリをC言語等で作成する
4. 4のようなAPIをすでにラップしたライブラリを利用する。


以下では、動作しない手法について調査してみた結果を記載します。

1. user.dirの値を変更する
1の方法は以下のAPIを利用することで変更できます。

System.setProperty("user.dir", ディレクトリ名);

この方法では、File(".")のディレクトリは変更されます。
しかしながら、取得対象のファイルは変更されません。

以下のようなサンプルプログラムを作成し確認すると分かりますが、
全てカレントディレクトリが変更されずにファイルの内容が出力されます。

import java.io.*;

/**
* user.dirのサンプルテスト
* カレントディレクトリにtest.txt、子ディレクトリ newcd、
* さらにnewcd配下にtest.txtを配置してください。
*
* @author Kiruah
*/
public class SampleUserDir {

/**
* メイン処理
*
* @param args 引数
*/
public static void main(String[] args) {

testUserDir();
}

/**
* user.dirのテスト
*/
private static void testUserDir() {

// 現在のuser.dirの値を利用してテスト
System.out.println("現在のuser.dirの値:" + System.getProperty("user.dir"));

File fileBeforeChange = new File(".");

System.out.println("現在のFile(\".\")が示すディレクトリ:" + fileBeforeChange.getAbsolutePath());
showFile(new File("test.txt"));

// user.dirの値を変更してテスト
System.setProperty("user.dir", System.getProperty("user.dir") + System.getProperty("file.separator") + "newcd");

File fileAfterChange = new File(".");

System.out.println("変更後のFile(\".\")が示すディレクトリ:" + fileAfterChange.getAbsolutePath());

showFile(new File("test.txt"));
}

/**
* ファイルの内容を表示
* 引数をStringに変更しても同じ結果になります。
*
* @param file 表示したいファイル
*/
private static void showFile(File file) {

BufferedReader br = null;

try {
br = new BufferedReader(new FileReader(file));

System.out.println("File:" + file.getAbsolutePath() + "の内容");
System.out.println(br.readLine());
} catch (Exception e) {
e.printStackTrace();
} finally {
if (br != null) {
try {
br.close();
} catch (Exception e) {
}
}
}
}
}

この方法では、new File(".")の位置が変っても、java.io.〜系のファイル取得位置までは変らないためカレントディレクトリ変更による意図した期待結果は得られません。

2. コマンドプロンプト経由でcd(等のOS依存)コマンドを実行する
この方法は、実行時にOSのコマンドを利用してディレクトリを変更しようとする方法です。
ですが、この方法でもディレクトリは変更できません。

この方法では、例えば以下のようにJavaプログラム中からcmd(Windowsのコマンド)を経由してcdコマンドを発行しています。
cmdのオプション/Kは、cdコマンドの実行が終わっても、念のためcmdが終了してしまわないようにしています。
実行すると分かるとおり、ディレクトリは変更されていません。

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;

/**
* OSのコマンドを利用する場合のサンプルテスト。
* サンプルはWindowsの場合。
*
* @author Kiruah
*/
public class SampleShell {

/**
* メイン処理
*
* @param args 引数
*/
public static void main(String[] args) {

testUserDir();
}

/**
* user.dirのテスト
*/
private static void testUserDir() {

showFile(new File("test.txt"));

try {
Runtime.getRuntime().exec(new String[] {"cmd", "/K", "cd", "newcd"});
} catch (IOException e) {
e.printStackTrace();
}

showFile(new File("test.txt"));
}

/**
* ファイルの内容を表示
* 引数をStringに変更しても同じ結果になります。
*
* @param file 表示したいファイル
*/
private static void showFile(File file) {

BufferedReader br = null;

try {
br = new BufferedReader(new FileReader(file));

System.out.println("File:" + file.getAbsolutePath() + "の内容");
System.out.println(br.readLine());
} catch (Exception e) {
e.printStackTrace();
} finally {
if (br != null) {
try {
br.close();
} catch (Exception e) {
}
}
}
}
}

この理由は単純です。このJavaのプロセスと、Javaから実行したcmdは異なるプロセス(子プロセス)になります。よって、子プロセス上でいくらディレクトリを変更したところで、別のプログラムとして認識されているのですからJava側はディレクトリは変更されません。

3. Win32 APIのSetCurrentDirectory()等、OSのAPIをラップしたライブラリをC言語等で作成する
この方法が一番現実的ではありますが、一番難易度が高い方法です。
OSのAPIを利用してディレクトリを変更します。よって、確実に変更できます。ただし、new File(".")とは同期がとれませんので、user.dirの値も同時に同じにしておくと良いと思います(user.dirの値はJava起動時に決定されるため、プログラム実行中は自動的に変更されません)。

この方法は、OSによってC言語側で記述する内容が異なります。またJNIを利用する必要もあり面倒です。
Windowsの場合の作成手順としては、
1. C言語でJNIとWin32 APIを利用してSetCurrentDirectory()を呼び出すプログラムを作成する(DLLで作成すると良い)。
2. 1のプログラムをコンパイルする。
3. Java側で2のライブラリをロードするJavaプログラムを作成し、ロード後、C言語のプログラムを実行する。

この方法については詳細を省略しますが、実現は可能です。

4. 3のようなAPIをすでにラップしたライブラリを利用する。
3のような方法は以外に面倒ですが、Windowsに限っては実はこのAPIをラッピングしたライブラリがあります。
そのライブラリを利用することで、ディレクトリの変更を実現できます。

SWT WIN32 EXTENSIONというSWTライブラリの拡張を利用します。
このライブラリはEclipseでも利用されているSWTのうち、Windows版のみを本当に拡張したライブラリという位置づけです。
このライブラリではUNIX等ではディレクトリ変更できませんので、ご注意ください。

また、このライブラリはSWTを必要とします。SWTを別途ダウンロードする必要がありますので、その点も注意が必要です。
ただし、Eclipseを利用している方はEclipseの中にすでにSWTのjarファイルがあります。

1) SWTを配置します
Eclipseのpluginsフォルダから以下のようなorg.eclipse.swt.win32で始まるファイルをコピーし、
プロジェクトに配置、ビルドパスに通してください。
org.eclipse.swt.win32.win32.x86_3.5.0.v3550b.jar

2) SWT WIN32 EXTENSIONをダウンロードし、以下のファイル(このファイル名はバージョン1.0.5のものです。ファイル名は今後のバージョンアップで変更される可能性があります。ご注意ください)をビルドパスに通します。
org.sf.feeling.swt.win32.extension_1.0.5.v20081205.jar

3) あわせて、swt-extension-win32.dll ファイルをプロジェクトのカレントディレクトリかSystem32フォルダなど、
PATHが通っているディレクトリに配置します。

4) 以下のようにFileSystem.setCurrentDirectory()を実行することで、ディレクトリを変更できます。

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;

import org.sf.feeling.swt.win32.extension.io.FileSystem;

/**
* SWT WIN32 EXTENSIONを利用した場合のサンプルプログラム
* サンプルはWindowsの場合。ご利用は自己責任でお願いします。
*
* @author Kiruah
*/
public class SampleSwtWin32Ext {

/**
* メイン処理
*
* @param args 引数
*/
public static void main(String[] args) {

testUserDir();
}

/**
* user.dirのテスト
*/
private static void testUserDir() {

showFile(new File("test.txt"));

FileSystem.setCurrentDirectory("newcd");

showFile(new File("test.txt"));
}

/**
* ファイルの内容を表示
* 引数をStringに変更しても同じ結果になります。
*
* @param file 表示したいファイル
*/
private static void showFile(File file) {

BufferedReader br = null;

try {
br = new BufferedReader(new FileReader(file));

System.out.println("File:" + file.getAbsolutePath() + "の内容");
System.out.println(br.readLine());
} catch (Exception e) {
e.printStackTrace();
} finally {
if (br != null) {
try {
br.close();
} catch (Exception e) {
}
}
}
}
}

この場合ですと、正しくディレクトリが変更され、newcdディレクトリに配置してあるtest.txtファイルの内容が表示されます。
ただし、jarやdllなどのおまけが付くため、若干利便性は劣るといえます。


【まとめ】
Javaでは標準ではカレントディレクトリの変更方法は、現在のところ提供されていません。
それは現在のバージョンではという話です。将来的にはバージョンがあがり、そういう機能も提供されるかもしれませんが、
現在のところはOSに依存した(Javaではない)APIを利用する方法しかありません。
ただし、Windowsは自作せずともライブラリが存在しますので、そういったライブラリを探して利用する方法があります。
他のOSでも有志の方が作成されているかもしれませんので、探してみると良いと思います。

余談ですが、SWTもSWT WIN32 EXTENSIONも、中身を見るとかなりのWin32 APIがラッピングされていることに気づきます。
これだけのことができれば、相当いろいろなことができます。
が、下手に利用するとシステムを破壊しかねないケースもあると思いますので、利用の際はご注意ください。
ちなみに、申し訳ありませんが、私は責任は取れませんのでご注意ください。
posted by Kiruahさん at 12:51| Comment(0) | TrackBack(0) | ノウハウ
この記事へのコメント
コメントを書く
お名前: [必須入力]

メールアドレス: [必須入力]

ホームページアドレス:

コメント: [必須入力]

認証コード: [必須入力]


※画像の中の文字を半角で入力してください。
この記事へのトラックバックURL
http://blog.sakura.ne.jp/tb/32099436
※ブログオーナーが承認したトラックバックのみ表示されます。
※言及リンクのないトラックバックは受信されません。

この記事へのトラックバック