【Java】【Android】iniファイルを読み込む

# 00. はじめに
Javaで設定値をファイルに保存するには.propertiesファイルとして保存する、というやり方があります。
しかし、C++で作成したアプリをJavaで作り直すなどで.iniファイルを使用したい時があります（というか、この間あった）。
なので、ちょっと、読み込む処理を書いてみます。

# 01. 読み込み対象の.iniファイルについて
なんか.iniファイルって厳密なフォーマットがないようなので、今回は以下のフォーマットを対象にします。

1. **コメント行**<br>
'#'で始まる行をコメント行として無視します。<br>
文途中からコメントを始められないこととします。
2. **セクション行**<br>
'['で始まり、']'で終わる行をセクション行としてみなし、囲われた文字列をセクションとします。<br>
以降で検出されたパラメータは、このセクションに属するものとします。
3. **パラメータ行**<br>
文中に'='がある行をパラメータ行とし、初めに登場した左側をkey、右側をvalueとします。
4. **上記以外**<br>
無視

# 02. 出来上がったものがこちらになります。

```java:IniFileLoader.java
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

/**
 * IniFileを読み込むローダー
 */
public class IniFileLoader {
    // HashMap<section, HashMap<key, String>>とする
    private HashMap<String, HashMap<String, String>> mDataMap;

    // ファイルパス
    private String mFilePath;

    // load完了しているか
    private boolean mIsLoaded = false;

    /**
     * 指定したファイルをロードする
     *
     * @param filePath ファイルパス
     * @return ロードに成功したかどうか
     */
    public boolean load(String filePath) {
        mFilePath = filePath;
        return loadProcess(filePath);
    }

    /**
     * 前回ロードを試みたファイルをもう一度ロードする
     *
     * @return ロードに成功したかどうか
     */
    public boolean reload() {
        return isEmpty(mFilePath) && loadProcess(mFilePath);
    }

    private boolean loadProcess(String filePath) {
        mIsLoaded = false;
        mDataMap = new HashMap<>();
        try {
            FileReader fileReader = new FileReader(new File(filePath));
            BufferedReader br = new BufferedReader(fileReader);
            String line = br.readLine();

            // セクション名
            String section = null;
            // キーバリュー
            HashMap<String, String> map = new HashMap<>();
            while (line != null) {
                // 行頭、行末の空白を取り除く
                line = line.trim();

                // 空行
                if (isEmpty(line)) {
                    // no process
                }
                // コメント行
                else if (line.charAt(0) == '#') {
                    // no process
                }
                // セクション行
                else if (line.charAt(0) == '[' && line.charAt(line.length() - 1) == ']') {
                    section = line.substring(1, line.length() - 1);
                    map = new HashMap<>();
                }
                // パラメータ行
                else if (line.length() >= 3 && line.contains("=") && line.length() > line.indexOf("=") + 1) {
                    String key = line.substring(0, line.indexOf("="));
                    String value = line.substring(line.indexOf("=") + 1);

                    map.put(key, value);
                    mDataMap.put(section, map);
                }

                line = br.readLine();
            }
            br.close();
        } catch (IOException e) {
            return false;
        }
        mIsLoaded = true;
        return true;
    }

    /**
     * 読み込んだ結果を (section, (key, value)) の {@link HashMap} で返す
     *
     * @return 読み込んだ結果の {@link HashMap} 
     */
    public HashMap<String, HashMap<String, IniItem>> getAllDataMap() {
        if (mIsLoaded) {
            return mDataMap;
        }
        return null;
    }

    /**
     * 読み込んだ結果の指定したセクションを(key, value)の {@link Map} で返す
     *
     * @param section セクション指定
     * @return 読み込んだ結果の指定したセクション部
     */
    public Map<String, String> getSectionDataMap(String section) {
        if (mIsLoaded) {
            return mDataMap.get(section);
        }
        return null;
    }

    /**
     * 指定したセクション、指定したキーの値を返す
     *
     * @param section セクション指定
     * @param key     キー指定
     * @return 指定したセクション、指定したキーの値
     */
    public String getValue(String section, String key) {
        if (mIsLoaded) {
            HashMap<String, String> map = mDataMap.get(section);
            if (map != null) {
                return map.get(key);
            }
        }
        return null;
    }

    /**
     * 指定したセクションが読み込んだ結果にあるかを返す
     *
     * @param section セクション指定
     * @return 存在する場合は {@code true}
     */
    public boolean containsSection(String section) {
        if (mIsLoaded) {
            return mDataMap.containsKey(section);
        }
        return false;
    }

    /**
     * 指定したセクションの、指定したキーが読み込んだ結果にあるかを返す
     *
     * @param section セクション指定
     * @param key     キー指定
     * @return 存在する場合は {@code true}
     */
    public boolean containsKey(String section, String key) {
        if (mIsLoaded) {
            HashMap<String, IniItem> map = mDataMap.get(section);
            return map != null && map.containsKey(key);
        }
        return false;
    }

    /**
     * {@code String} が空か判定する
     *
     * @param str 判定対象
     * @return {@code String} が空ならば {@code true}
     */
    private boolean isEmpty(String str) {
        return str == null || str.length() == 0;
    }
}
```

1行ずつ読んで判定しているだけですね。

# 03. ライブラリにしたものがこちらになります。
https://github.com/entan05/IniFileController

ライブラリにしました。
一応、Androidでも使用できるようになっています。

# 98. 参考
[Java][サンプルコード] INIファイル読み書きライブラリを自作してみた | http://javasampleokiba.blog.fc2.com/blog-entry-27.html

# 99. 更新履歴

|日付|内容|
|:--|:--|
|2018/03/24|投稿|