ini ファイル形式、”[ ]" で括ったセクションと呼ばれるカテゴリ毎の key と value を書いていくもの。
一般的には、セクションは必須だと思うが、次のルールで Java で読み込む。
・行区切りのテキストで、文字コードはデフォルトで UTF-8 で読込み、UTF-8以外は、文字コードを指定する
・[section] 必須ではない。
・[section] の次に、key = value あるいは、key : value で記述
・先頭文字 '#' or ';' ~ 行末までコメント
(読み込んだ後の機能)
* 内部で持つMap への section+Key 参照
* JSON への変換
読込み解析時に、
Throwable な Consumer → ThrowableConsumer
Throwable な Function → ThrowableFunction
を使用することで規則に沿わない ini ファイルの読込みでは例外を吐くようにしている。
この Throwable な関数型インターフェースは
GitHub - yipuran/yipuran-core: Java application framework
が提供しているのでこれを使用する
キーの重複などのエラーは、RuntimeException系の IllegalArgumentException を発生させるようにしている
import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.math.BigDecimal; import java.nio.charset.Charset; import java.nio.file.FileSystems; import java.nio.file.Files; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Optional; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Predicate; import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.Stream; import org.yipuran.function.ThrowableConsumer; import org.yipuran.function.ThrowableFunction; import com.google.common.reflect.TypeToken; import com.google.gson.Gson; import com.google.gson.GsonBuilder; /** * IniFileParser * * [section] 必須 * [section] の次に、key = value あるいは、key : value で記述 * シングル、ダブル クォート文字の特別な認識はしない。 * 行で区切り * 先頭 '#' or ';' ~行末までコメント * (機能) * 内部で持つMap への section+Key 参照 * JSON への変換 * */ public final class IniFileParser{ Map<String, List<Map.Entry<String, String>>> map = new HashMap<>(); // section 無しの Map : key and value Map<String, String> nosectionMap = new HashMap<>(); String key = null; private IniFileParser(Stream<String> stream) throws IllegalArgumentException{ Pattern allblank = Pattern.compile("^ +$"); Predicate<String> nosharp = e->!e.startsWith("#"); Predicate<String> noempty = e->e.length() > 0; Predicate<String> noblank = e->!allblank.matcher(e).matches(); AtomicInteger line = new AtomicInteger(0); AtomicReference<String> parseSection = new AtomicReference<String>(null); stream.peek(e->line.incrementAndGet()) .filter(nosharp.and(e->!e.startsWith(";")).and(noempty).and(noblank)) .map(ThrowableFunction.of(e->{ if (e.substring(0, 1).equals("[")){ if (!e.endsWith("]")) throw new IllegalArgumentException( "Line:" + line.get() + " section Error : " + e); String section = e.substring(1, e.length() - 1); if (map.containsKey(section)) throw new IllegalArgumentException( "Line:" + line.get() + " Duplicate secton Error : " + e); map.put(section, new ArrayList<>()); }else{ if (e.indexOf("=") < 0 && e.indexOf(":") < 0 ) throw new IllegalArgumentException( "Line:" + line.get() + " unsolved Error : " + e); } return e; })).forEach(ThrowableConsumer.of(e->{ if (e.substring(0, 1).equals("[")){ // section String section = e.substring(1, e.length() - 1); parseSection.set(section); }else{ String value = null; int x = e.indexOf("="); if (x > 0){ key = e.substring(0, x).trim(); value = e.substring(x+1); }else{ x = e.indexOf(":"); if (x > 0){ key = e.substring(0, x).trim(); value = e.substring(x+1); } } if (key.length()==0){ throw new IllegalArgumentException( "Line:" + line.get() + " Not key Error : " + e); } String section = parseSection.get(); // value if (section==null){ // No-section if (nosectionMap.containsKey(key)){ throw new IllegalArgumentException( "Line:" + line.get() + " Duplicate key Error : " + e); } nosectionMap.put(key, value); }else{ // has section List<Map.Entry<String, String>> list = map.get(section); if (list.size() > 0){ if (list.stream().anyMatch(t->t.getKey().equals(key))){ throw new IllegalArgumentException( "Line:" + line.get() + " Duplicate key Error : " + e); } } list.add(new _Entry(key, value)); } } })); } class _Entry implements Map.Entry<String, String>{ private String key; private String value; private _Entry(String key, String value){ this.key = key; this.value = value; } @Override public String getKey(){ return key; } @Override public String getValue(){ return value; } @Override public String setValue(String value){ String old = value; this.value = value; return old; } } public static IniFileParser read(InputStream in) throws IllegalArgumentException{ BufferedReader br = new BufferedReader(new InputStreamReader(in, Charset.forName("UTF-8"))); return new IniFileParser(br.lines()); } public static IniFileParser read(InputStream in, String charset) throws IllegalArgumentException{ BufferedReader br = new BufferedReader(new InputStreamReader(in, Charset.forName(charset))); return new IniFileParser(br.lines()); } public static IniFileParser read(String path) throws IOException, IllegalArgumentException{ return new IniFileParser( Files.lines(FileSystems.getDefault().getPath("", path), Charset.forName("UTF-8")) ); } public static IniFileParser read(String path, String charset) throws IOException{ return new IniFileParser( Files.lines(FileSystems.getDefault().getPath("", path), Charset.forName(charset)) ); } public static IniFileParser read(File file) throws IOException, IllegalArgumentException{ try(BufferedReader br = new BufferedReader( new InputStreamReader(new FileInputStream(file), Charset.forName("UTF-8")))){ return new IniFileParser(br.lines()); } } public static IniFileParser read(File file, String charset) throws IOException, IllegalArgumentException{ try(BufferedReader br = new BufferedReader( new InputStreamReader(new FileInputStream(file), Charset.forName(charset)))){ return new IniFileParser(br.lines()); } } public static IniFileParser read(Reader reader) throws IllegalArgumentException{ BufferedReader br = new BufferedReader(reader); return new IniFileParser(br.lines()); } public String getString(String key){ return Optional.ofNullable(nosectionMap.get(key)).map(e->e.trim()).orElse(""); } public String getString(String section, String key){ if (map.containsKey(section)){ return map.get(section).stream().filter(e->e.getKey().equals(key)).findAny() .map(e->e.getValue()).map(e->e.trim()).orElse(""); } return ""; } public Optional<Integer> getInteger(String key){ return Optional.ofNullable(nosectionMap.get(key)).map(e->e.trim()).map(Integer::valueOf); } public Optional<Integer> getInteger(String section, String key){ if (map.containsKey(section)){ return map.get(section).stream().filter(e->e.getKey().equals(key)).findAny() .map(e->e.getValue()).map(e->e.trim()).map(Integer::valueOf); } return Optional.empty(); } public Optional<Double> getDouble(String key){ return Optional.ofNullable(nosectionMap.get(key)).map(e->e.trim()).map(Double::valueOf); } public Optional<Double> getDouble(String section, String key){ if (map.containsKey(section)){ return map.get(section).stream().filter(e->e.getKey().equals(key)).findAny() .map(e->e.getValue()).map(e->e.trim()).map(Double::valueOf); } return Optional.empty(); } public Optional<BigDecimal> getBigDecimal(String key){ return Optional.ofNullable(nosectionMap.get(key)).map(e->e.trim()).map(e->new BigDecimal(e)); } public Optional<BigDecimal> getBigDecimal(String section, String key){ if (map.containsKey(section)){ return map.get(section).stream().filter(e->e.getKey().equals(key)).findAny() .map(e->e.getValue()).map(e->e.trim()).map(e->new BigDecimal(e)); } return Optional.empty(); } public boolean getBoolean(String key){ boolean b = Optional.ofNullable(nosectionMap.get(key)).map(e->e.trim()).map(e->{ if (e.equals("1")){ return true; }else if(e.equals("0")){ return true; } String s = e.toLowerCase(); if (s.equals("true")) return true; if (s.equals("false")) return false; throw new IllegalArgumentException("Not boolean value : " + e); }).orElse(false).booleanValue(); return b; } public boolean getBoolean(String section, String key){ if (map.containsKey(section)){ boolean b = map.get(section).stream().filter(e->e.getKey().equals(key)).findAny() .map(e->e.getValue()) .map(e->e.trim()) .map(e->{ if (e.equals("1")){ return true; }else if(e.equals("0")){ return true; } String s = e.toLowerCase(); if (s.equals("true")) return true; if (s.equals("false")) return false; throw new IllegalArgumentException("Not boolean value : " + e); }).orElse(false).booleanValue(); return b; } return false; } public List<Entry<String, String>> getEntry(String section){ return map.get(section); } public List<Entry<String, String>> getNosections(){ return nosectionMap.entrySet().stream().collect(Collectors.toList()); } public String toJson(){ Gson gson = new GsonBuilder().serializeNulls().create(); if (nosectionMap.size()==0) return gson.toJson(map , new TypeToken<Map<String, String>>(){}.getType()); Map<String, Object> allmap = new HashMap<>(); allmap.putAll(nosectionMap); allmap.putAll(map); return gson.toJson(allmap , new TypeToken<Map<String, String>>(){}.getType()); } }
呼出しサンプル
sample.ini ファイル
# sample.ini ; テストコメント keys = a, b, c [customer] city = tokyo name : ヤマダ master : TRUE [server] host = localhost port = 20718 password =
Java 実行例
try(FileInputStream in = new FileInputStream(ClassLoader.getSystemClassLoader() .getResource("sample.ini").getPath()) ){ IniFileParser iniParser = IniFileParser.read(in); System.out.println("keys = [" + iniParser.getString("keys") + "]" ); System.out.println( "customer.name = [" + iniParser.getString("customer", "city") + "]" ); System.out.println( "customer.name = [" + iniParser.getString("customer", "name") + "]" ); System.out.println( "customer.master = [" + iniParser.getBoolean("customer", "master") + "]" ); System.out.println( "server.port = [" + iniParser.getInteger("server", "port").orElseThrow() + "]" ); System.out.println( "server.name = [" + iniParser.getString("server", "password") + "]" ); String json = iniParser.toJson(); System.out.println(json); }catch(IOException e){ e.printStackTrace(); }