石橋を叩いて壊すページ

config.ymlの読み書きと、マルチバイト文字の読み出しに対応してみた

Bukkitには、設定を簡単に保存できるFileConfigurationインターフェースが用意されている。 今回はこれの使い方をまとめてみた。

データの設定と読み込み

まずは簡単なところから始めてみよう。データを設定して、すぐ読み込むコードを書いてみる。
以下のコードでは、「key」という名前で「value」という値を設定し、その後すぐに「key」の値を画面に表示している。

package jp.jias.bukkit;

import org.bukkit.Bukkit;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.plugin.java.JavaPlugin;

public class TestPlugin extends JavaPlugin{

	/**
	 * プラグインが有効化されるとき呼び出される
	 */
	public void onEnable(){

		// 設定データ入出力クラスを作る
		FileConfiguration conf=new YamlConfiguration();

		// 「キー」と「値」の形式で、データを設定する
		conf.set("key","value");

		// 設定「key」の値を取得し、画面に表示する
		Bukkit.broadcastMessage("設定 key の値は " + conf.getString("key") + " です。");
	}
}

上記のコードをビルドしてBukkitに読み込ませる。
その後ゲームにログインして/reloadコマンドを実行すると、以下のように表示されるはずだ。


画面下のほうに、設定した値が正しく表示されている。

BukkitのFileConfigurationでは、このようにデータを「名前」と「値」の形式で保存している。
setメソッドで「名前」と「値」を保存し、getStringメソッドで「名前」を与えて対応している「値」を取り出している。

今回の値は文字列だったが、数値やリストなども保存できる。それぞれ、整数を取り出すためにはgetInt、リストを取り出すにはgetListなど、
取り出すデータのデータ型によって多数のgetメソッドが定義されている。
なお値の設定に関しては、setStringやsetIntというメソッドはなく「set」メソッドがどんなデータ型のデータも受け付けるようになっている。
このため値を設定するときは常にただ単に「set」メソッドを呼べばいい。

以下に取り扱えるデータ型の読み書きの例を挙げるが、ほかにも少数やVectorなども保存できる。
詳しくはConfigurationSectionクラスを調べてみて欲しい。

// 設定データ入出力クラスを作る
FileConfiguration conf=new YamlConfiguration();

// 文字列をsetで設定
conf.set("sample1","sample text");

// 文字列をgetStringで取り出す
Bukkit.broadcastMessage("getString: " + conf.getString("sample1"));

// 整数をsetで設定
conf.set("sample2",12345);

// 整数をgetIntで取り出す
Bukkit.broadcastMessage("getInt: " + conf.getInt("sample2"));

// 以後同様に…

// リスト(List)をsetで設定、getListで取り出し
List<String> list=new ArrayList<String>();
list.add("list1");
list.add("list2");
list.add("list3");
conf.set("sample3",list);
Bukkit.broadcastMessage("getList: " + conf.getList("sample3"));

// 論理値(Boolean)の設定はset、取り出しはgetBoolean
conf.set("sample4",true);
Bukkit.broadcastMessage("getBoolean: " + conf.getBoolean("sample4"));

// マップのリスト(List<Map<?,?>>)をsetで設定、getMapListで取り出し
Map<String,String> map1=new HashMap<String,String>();
map1.put("map1A","value1A");
map1.put("map1B","value1B");

Map<String,String> map2=new HashMap<String,String>();
map2.put("map2A","value2A");
map2.put("map2B","value2B");

List<Map<String,String>> maplist=new ArrayList<Map<String,String>>();
maplist.add(map1);
maplist.add(map2);
conf.set("sample5",maplist);
Bukkit.broadcastMessage("getMapList: " + conf.getMapList("sample5"));

// なんとアイテムスタックも同様の方法で設定・取り出しできる
ItemStack item=new ItemStack(Material.APPLE);
conf.set("sample6",item);
Bukkit.broadcastMessage("getItemStack: " + conf.getItemStack("sample6"));


保存した値が正しく返ってきている。

データのファイルへの保存と読み込み

上記では設定したデータをすぐ読み込んでいたが、設定したデータをファイルに保存したり、ファイルから読み込む機能がPluginクラスに用意されている。
データを保存するにはsaveConfig()メソッドを、読み込むにはgetConfig()メソッドを叩けばいい。例えば以下のようになる。

package jp.jias.bukkit;

import org.bukkit.Bukkit;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.plugin.java.JavaPlugin;

public class TestPlugin extends JavaPlugin{

	/**
	 * プラグインが有効化されるとき呼び出される
	 */
	public void onEnable(){

		// 設定ファイルを読み込む
		FileConfiguration conf=getConfig();

		// 設定「key」の値を取得し、画面に表示する
		Bukkit.broadcastMessage("設定 mine の値は " + conf.getString("mine") + " です。");

		// 「名前」と「値」の形式で、データを設定する
		conf.set("mine","craft");

		// データを設定ファイルに保存する
		saveConfig();
	}
}

上記の例ではgetConfig()でファイルに保存したデータを読み込み、名前「mine」の値を画面に表示している。
その後、名前「mine」に値「craft」を設定し、saveConfig()でファイルにデータを保存している。
この結果、初回実行時は「mine」に値が設定される前なので、画面には「設定 mine の値は null です。」と表示される。
2回目以降の実行時には、「mine」に値が設定されているので、画面には「設定 mine の値は craft です。」と表示される。


初回起動時にはnullが表示されているが、再読み込み時にはcraftの文字が読み込まれている。

saveConfig()メソッドを叩くと、Bukkitの「plugins/プラグイン名」フォルダの下に「config.yml」というテキストファイルが生成される。
メモ帳などで中身を開くと、以下のようにデータが記録されている。

mine: craft

見てのとおり、コロン(:)の左が名前、右が値の形式で保存されていると分かる。
これはYAMLというデータ形式で、形式を覚えておけばメモ帳などで直接データを編集し、プラグインに読み込ませることもできる。

なお、config.ymlの文字コードはOSによって異なり、WindowsであればシフトJIS、UNIX系OSではUTF-8で書かなければならない。
もし文字コードを間違えると、文字化けが起こったり、プラグインの起動時に文字コード不正の旨のエラーが表示されるのでご注意いただきたい。
文字コードを共通化する方法については後述する。

デフォルト値を用意する

上記では、データをセーブ・ロードできるsaveConfig()・getConfig()について紹介したが、 Pluginクラスにはもうひとつ、saveDefaultConfig()というメソッドが用意されている。
このメソッドは、もしBukkitの「plugins/プラグイン名」フォルダの下に「config.yml」に保存されていなければ、プラグインのJARファイルのなかに保存されているconfig.ymlを読み込んで、プラグインデータフォルダに保存するというものである。
もしプラグインデータフォルダにファイルがあれば、saveDefaultConfig()は何もしない。
これによって、初回起動時(保存ファイルがない時)だけ、初期値を保存するということができる。

saveDefaultConfig()を活用するには、まずデフォルトの設定ファイルを作らなければならない。
プラグインのplugin.ymlファイルがあるフォルダと同じ場所に、config.ymlという名前のファイルを作り、ここにYAML形式で初期データを入れておけばいい。
なお、ファイル名を1文字でも間違うと動かないのでその点は注意。


resourcesフォルダを右クリックし、新規→ファイル、を選択。ファイル名を「config.yml」にして保存。
できたファイルを開き、「key: value」と書いて保存。

saveDefaultConfig()を使ったコードは以下のようになる。

package jp.jias.bukkit;

import org.bukkit.Bukkit;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.plugin.java.JavaPlugin;

public class TestPlugin extends JavaPlugin{

	/**
	 * プラグインが有効化されるとき呼び出される
	 */
	public void onEnable(){

		// もし設定ファイルがまだなければ、デフォルトの設定を保存する
		saveDefaultConfig();

		// 設定ファイルを読み込む
		FileConfiguration conf=getConfig();

		// 設定「key」の値を取得し、画面に表示する
		Bukkit.broadcastMessage("設定 key の値は " + conf.getString("key") + " です。");
	}
}

上記コードを実行すると、もしプラグインデータフォルダにconfig.ymlファイルがなければ、JARファイル内に用意したconfig.ymlがコピーされ、画面には「設定 key の値は value です。」と表示されたはずだ。
もしプラグインデータフォルダにconfig.ymlファイルが既にあれば、saveDefaultConfig()は何もしない。

データ型がネストしていても大丈夫

ここからは応用。 保存できるデータ型については上記したとおりだが、FileConfigurationはそれらのデータ型が入れ子(ネスト)になっていても問題なく保存できる。
たとえば、Listの中にMapがあり、そのまたMapの中にListがあり、そのまたListのなかにStringがあるような状態でも、 FileConfigurationはそれぞれを適切にconfig.ymlに書き出し、また読み取ることができる。

実際にやってみよう。以下のコードはList<Map<Integer,List<String>>>型のデータを保存して読み込んでいる。

package jp.jias.bukkit;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.bukkit.Bukkit;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.event.Listener;
import org.bukkit.plugin.java.JavaPlugin;

public class TestPlugin extends JavaPlugin implements Listener{

	/**
	 * プラグインが有効化されるとき呼び出される
	 */
	public void onEnable(){

		// 設定ファイルを読み込む
		FileConfiguration conf=getConfig();

		// 設定「test」の値を全部表示する
		Bukkit.broadcastMessage("設定 test の値は " + conf.getString("test") + " です。");

		// 読み込んだデータに名前が「test」のデータがあるか
		if(conf.contains("test")){

			// まず一番外側のListとその内側のMapを入手する
			List<Map<?,?>> list3=conf.getMapList("test");

			// その内側のListを取り出す
			List<?> list1=(List<?>) list3.get(0).get(1234);

			// その中の値を表示する
			Bukkit.broadcastMessage("Listの中のMapの中のListの中の値:" + list1.get(0));
		}

		// List<Map<Integer,List<String>>>を作ってみる
		// まずはList<String>を作る
		List<String> list1=new ArrayList<String>();
		list1.add("mine");
		list1.add("craft");

		List<String> list2=new ArrayList<String>();
		list2.add("mojang");
		list2.add("bukkit");

		// ListをMapにいれる
		Map<Integer,List<String>> map=new HashMap<Integer,List<String>>();
		map.put(1234,list1);
		map.put(5678,list2);

		// MapをListにいれる
		List<Map<Integer,List<String>>> list3=new ArrayList<Map<Integer,List<String>>>();
		list3.add(map);

		// 「名前」と「値」の形式で、データを設定する
		conf.set("test",list3);

		// データを設定ファイルに保存する
		saveConfig();
	}
}


初回実行時は「test」に値が設定される前なので、画面には「設定 test の値は null です。」と表示される。
2回目以降の実行時には、「test」に値が設定されているので、画面には「設定 test の値は(略)です。」と表示される。
Listの中のMapの中のListの中の値にもアクセスできていることが分かる。

ちなみに、このデータを保存したconfig.ymlをメモ帳などで開いてみると以下のようになっている。
少し分かりにくいが、「- 」がリストの要素を、「A: B」がマップ(キーがA、値がB)の要素を、スペース2文字の字下げが階層を表していることが読み取れる。

test:
- 5678:
  - mojang
  - bukkit
  1234:
  - mine
  - craft

文字コードを共通化する

既に書いたとおり、config.ymlはOS依存の文字コードで読み込まれる。
しかし、たとえばWindows環境でプラグインを開発・テストして、UNIX環境で運用するような人や、プラグインを配布する人は、環境によって文字コードが異なるのは大問題だ。
文字コードを共通化するには、以下のようにすればよい。

package jp.jias.bukkit;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;

import org.bukkit.Bukkit;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.plugin.java.JavaPlugin;

public class TestPlugin extends JavaPlugin{

	/** 設定ファイルの文字コード(UTF-8の場合) */
	static final private Charset CONFIG_CHAREST=StandardCharsets.UTF_8;

	/** 設定ファイルの文字コード(シフトJISの場合) */
//	static final private Charset CONFIG_CHAREST=Charset.forName("windows-31j");

	/**
	 * プラグインが有効化されるとき呼び出される
	 */
	public void onEnable(){

		// もし設定ファイルがまだなければ、デフォルトの設定を保存する
		saveDefaultConfig();

		// プラグインフォルダ上の設定ファイルのパスを作る
		String confFilePath=getDataFolder() + File.separator + "config.yml";

		// 設定ファイルを開く
		try(Reader reader=new InputStreamReader(new FileInputStream(confFilePath),CONFIG_CHAREST)){

			// 設定データ入出力クラスを作る
			FileConfiguration conf=new YamlConfiguration();

			// 設定を読み込む
			conf.load(reader);

			// 設定「key」の値を取得し、画面に表示する
			Bukkit.broadcastMessage("設定 key の値は " + conf.getString("key") + " です。");
		}catch(Exception e){
			// エラーが起きた場合はその旨を表示する
			System.out.println(e);

			// 起動失敗のためこのプラグインを無効にする
			onDisable();
		}
	}
}

手順はやや面倒くさいが、まずファイルを開いてInputStreamを取得し、文字コードを指定してReaderを用意し、そのReaderをFileConfiguration#loadに渡してデータを読み込ませる。
これで、指定した文字コードでファイルを読んでくれるようになる。
なお、現状動作を見る限りsaveDefaultConfig()メソッドは文字コードに関係なく動くようなので、今回はそのまま使用している。

上記コードで使用しているPluginクラスのgetDataFolder()メソッドは、プラグインデータフォルダ、つまりbukkitの「plugins/プラグイン名」フォルダのパスを取得するもので、これにセパレータ(Windowsだと\、Linuxだと/など)とファイル名をつなげることで、プラグインデータフォルダ上のconfig.ymlを読み込むことができる。
もしJARファイルに入っているconfig.ymlを読みたいのなら、同じくPluginクラスのgetResource()メソッドを使い、以下のようにすればよい。

try(Reader reader=new InputStreamReader(getResource("config.yml"),CONFIG_CHAREST)){…

try(…){}という書き方は、Java7から可能になったリソースつきtry文とか自動クローズとか呼ばれるもので、tryブロックが終わるころにはreaderは自動でcloseされているという便利なものである。

ここでひとつ注意がある。せっかくマルチバイト文字を読み込んでも、saveConfig()を叩くとマルチバイト文字が以下のように文字化けした状態で保存されてしまう。

saveConfig()前:key: マインクラフト
                     ↓
saveConfig()後:key: "\u7e5d\u69ed\u3046\u7e5d\uff73\u7e67\uff6f\u7e5d\uff69\u7e5d\u8f14\u30e8"

保存されたデータをgetConfig()しても文字化けして読み込まれてしまう。
JavaだしどうせUTF-16で書かれてるんだろう…とあたりをつけて試行錯誤してみたが、どうしても元の文字列には戻らなかった。
セーブ時に文字化けを防ぐ方法も残念ながら見つからなかった。

そんなわけで、マルチバイト文字を含む設定ファイルを保存するには、何か別の方法を用いなければならないようだ。
しかし保存さえしなければ文字化けは起こらないので、保存する必要がないデータに関してはFileConfigurationを便利に使えるだろう。
たとえばJavaソース上に直接書かれたエラーメッセージなどを設定ファイルに移動しておけば、設定ファイルを翻訳・差し替えするだけでプラグインを多国語で展開することができる。

整数・文字列・アイテムスタックなどのように直接設定ファイルに書き込めるものはいいが、 そうでないものは直接書ける形に変換したり、変換した結果を逆変換したりしなくてはならない。
そのあたりはまた後日書く。

この記事を評価

この記事にコメント

  1. ...

【この記事にコメント】
お名前:
コメント:

Menu