きどたかのブログ

いつか誰かがこのブログからトラブルを解決しますように。

java.util.prefsパッケージの基本的な使い方

なかなか使う機会もないパッケージです。

JDK1.4からと、思ったよりも古くからあるようです。 

 

Windowsではレジストリが使われてます。

システムルート

 HKEY_LOCAL_MACHINE\SOFTWARE\JavaSoft\Prefs

ユーザールート

 HKEY_CURRENT_USER\Software\Java Soft\Prefs

 

Windowsの場合は、

java.util.prefs.WindowsPreferencesFactoryから生成されたもので

java.util.prefs.WindowsPreferencesというクラスが

java.util.prefs.Preferenceのインスタンスとして使われます。

 

上記のファクトリは次のファイルを作成して実装を差し替え可能です。

META-INF/services/java.util.prefs.PreferencesFactory

普通は自分で登録する必要はないです。

たぶんJavaレジストリをいじりたくて、

でもあのシステムルート/ユーザールートの配下しかいじれないのだと意味がない人が、その実装(レジストリをいじれるJNIコード含む)を用意して差し替えるということじゃないでしょうか。

ええ、ただのJavaエンジニアにはハードル高いですよ。

 

以下のファクトリクラス決定順序は、
PreferencesクラスのJavadocに記載されている順序です。

-システムプロパティ 

-META-INF/services/java.util.prefs.PreferencesFactory

-OSが何かを調べて自動で決定
 
おそらく、通常の状態だと、一番最後の条件にかかります。

OracleJDKではシステム・プロパティはなかった。

IBM JDKはシステム・プロパティに登録されてました。

 

その他のプラットフォームではレジストリなんてないので、

 java.util.prefs.FileSystemPreferencesという実装が使われてるみたいで、

ファイルシステム上にXMLファイルでストアされるっぽいです。

z/OS上でもそうだった。妙なWarningが出るので、今後使う気はないですが。

WARNING: Could not create system preferences directory. System preferences are unusable.

だとか

WARNING: Prefs file removed in background /パス名は臥せる/.systemPrefs/prefs.xml

それに

WARNING: Prefs file removed in background /.java/.userPrefs/prefs.xml

 

スーパーユーザーで動かしていて、

ホームディレクトリがルートっぽかったので確証持てませんが、

きっとユーザーホームを意識してファイルが出来ます。 

ユーザーホーム/.java/.userPrefs/prefs.xml

ノードは、ファイルシステムのディレクトリとして機能します。

ユーザーホーム/.java/.userPrefs/test/prefs.xml

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE map SYSTEM "http://java.sun.com/dtd/preferences.dtd">

<map MAP_XML_VERSION="1.0">

 <entry key="testKey" value="testValue"/>

</map>

 

ざっくりとした使い方(申し訳ないが例外は全部スローさせてる)

まずはこういう感じでユーザールートを取得します。

※)システムルートは恐いからいじらない

Preferences userRoot = Preferences.userRoot();

 

1つ階層を作りたいのであればノードを掘る。

Preferences child  = userRoot.node("child");

 

設定を残したいのならばputする。

child.put("key","value");

キーは必ずString、バリューは様々な型を使えるようになっています。

 

Windowsで試してみて気に食わなかった点がある。

ここでこんな名前でノードを作ったり、

キーやバリューを設定した時に嫌な感じに保存される。

Preferences child  = userRoot.node("testChild");

child.put("testKey","testValue");

 
レジストリに出来るキー(フォルダ):test/Child
レジストリに出来る文字列値の名前:test/Key
レジストリに出来る文字列値のデータ:test/Value
 
ノードについても、"test/Child"というのが出来るのであって、
testの下にChildがあるわけではない。
 
何個か試したところ、大文字に反応します。
大文字の前には全てスラッシュが入る。
他にもルールがあるようですが、詳しく知る必要がある人は、
java.util.prefs.WindowsPreferencesのJavadocに目を通してください。

見た目が醜くなるので大文字は使わない方がいいですね。

 

NodeChangeListenerとPreferenceChangeListener

NodeChangeListenerの実装を用意して

node.addNodeChangeListener(new NodeChangeListenerImpl();

という風にリスナーを登録しておくと、

ノードの追加・削除の際に動かしてくれます。

NodeEventChangeが引数で渡ってくるので、

どういうノードが追加されたかを見ることができます。

ただし、追加されたノード(Preferences)がすぐに削除されていた場合、

そのPreferencesへの操作でIllegalStateExceptionが発生することがあります。

新しいノードに対して新しいリスナーを登録したい場合に注意が必要です。

このリスナーは、子ノードの追加・削除が対象で、

自ノードの削除には反応しません。

 

PreferenceChangeListenerの実装を用意して

node.addPreferenceChangeListener(new PrefenceChangeListenerImpl();

という風にリスナーを登録しておくと、
キー・バリューの変更の際に動かしてくれます。
引数としてPreferenceChangeEventが渡ってきます。
これからキーと新しい値を見れます。
更新の場合でも新しい値のみで、古い値を受け取ることは出来ません。
このリスナーは、自ノードのキー・バリューに反応だけで、
子ノードのキー・バリューの変更には反応しません。
 
これらのリスナーは、メインスレッドとは別のスレッドで動きます。
どうもリスナーを動かすスレッドは1つのみのようで、
子ノードの追加、自ノードにキー・バリューを登録とやると、
PreferenceChangeListenerは待たされてます。
おそらくは深い階層のノード群で、全てリスナーを登録しようとも、
リスナー用のスレッドは1つしかない気がします。

 

systemNodeForPackageとuserNodeForPackage

引数にクラスを渡すことで、パッケージ名(クラス名は使わない)から、

深くまでノードを掘ってくれるものです。

実際、私のPCには、これで作ったと思われるレジストリがありました。

ただ残念なことに、パッケージ名の先頭のJPだけ大文字で"/J/P"というのなってます。

 

nodeメソッドに渡すパス名のコンベンション

node.node("jp.co.hoge");

この場合は、そのままの名前でレジストリにキーができます。

node.node("jp/co/hoge");

この場合は、階層になってレジストリにキーができます。

さきほどのuserNodeForPackageなどは、

クラスからパッケージ名を取り出して、

ピリオドをスラッシュに変えてるのだと言えます。

パッケージに基づきたいが、階層にするのが嫌だと思う人は、

node.node(Hoge.class.getPackage().getName());

という風に作ることで階層なしでいけます。

 

他にもメソッドがあって考える必要がありますが、

主だった使い方はここに書けたかと思います。

いちおうWindows7 64bitで動かしてました。